mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e87d4b5af0 | |||
| 0a19f03d99 | |||
| 556ea285db | |||
| a34a1d598c | |||
| b6bb8c26fb | |||
| a3004aea15 | |||
| 8d7d152b87 | |||
| 04c702f617 | |||
| 842733a582 | |||
| f1d9a79214 | |||
| f97b8f403f | |||
| b5cc212e75 | |||
| 45dbc03636 | |||
| a22e1dd557 | |||
| ad0298c5a1 | |||
| 039cf28484 | |||
| 6702fac9af | |||
| 94ef56b8b3 | |||
| 5b0ca7c58a | |||
| 5d417ee6e8 | |||
| 9a1d077d9c | |||
| a2f92e92b5 | |||
| 3950ab5741 | |||
| 200258c2a0 | |||
| e0d0582d97 | |||
| da9b925066 | |||
| 736ba2937e | |||
| fe5baaf228 | |||
| 5fd7bc0d74 | |||
| 2d6c4af15e | |||
| 3f089980e4 | |||
| 25493c674b | |||
| fe7768f54d | |||
| c7055ebf84 | |||
| ed95e3d325 | |||
| 4f23c44a68 | |||
| e35ac39418 | |||
| f4dbb093eb | |||
| 6aa14d58ff | |||
| f3827116a3 | |||
| be83aacf80 | |||
| a58b402fc8 | |||
| 56cc398564 | |||
| 7aa5d784d7 | |||
| 5f285e4df4 | |||
| 67d34cebf7 | |||
| b633af5875 | |||
| 2c3d4a7f15 | |||
| 2c20ae1fef | |||
| 8a30a35b11 | |||
| 7e485e614c | |||
| e1f59b5987 | |||
| 04c79bb941 | |||
| 4158c70f58 | |||
| a9450f35cc | |||
| 08091ba9fb | |||
| 19f3bc00ee |
@@ -5,6 +5,46 @@ information on the list of deprecated flags and APIs please have a look at
|
||||
https://docs.docker.com/misc/deprecated/ where target removal dates can also
|
||||
be found.
|
||||
|
||||
## 1.9.1
|
||||
|
||||
## Runtime
|
||||
|
||||
- Do not prevent daemon from booting if images could not be restored (#17695)
|
||||
- Force IPC mount to unmount on daemon shutdown/init (#17539)
|
||||
- Turn IPC unmount errors into warnings (#17554)
|
||||
- Fix `docker stats` performance regression (#17638)
|
||||
- Clarify cryptic error message upon `docker logs` if `--log-driver=none` (#17767)
|
||||
- Fix seldom panics (#17639, #17634, #17703)
|
||||
- Fix opq whiteouts problems for files with dot prefix (#17819)
|
||||
- devicemapper: try defaulting to xfs instead of ext4 for performance reasons (#17903, #17918)
|
||||
- devicemapper: fix displayed fs in docker info (#17974)
|
||||
- selinux: only relabel if user requested so with the `z` option (#17450, #17834)
|
||||
- Do not make network calls when normalizing names (#18014)
|
||||
|
||||
## Client
|
||||
|
||||
- Fix `docker login` on windows (#17738)
|
||||
- Fix bug with `docker inspect` output when not connected to daemon (#17715)
|
||||
- Fix `docker inspect -f {{.HostConfig.Dns}} somecontainer` (#17680)
|
||||
|
||||
## Builder
|
||||
|
||||
- Fix regression with symlink behavior in ADD/COPY (#17710)
|
||||
|
||||
## Networking
|
||||
|
||||
- Allow passing a network ID as an argument for `--net` (#17558)
|
||||
- Fix connect to host and prevent disconnect from host for `host` network (#17476)
|
||||
- Fix `--fixed-cidr` issue when gateway ip falls in ip-range and ip-range is
|
||||
not the first block in the network (#17853)
|
||||
- Restore deterministic `IPv6` generation from `MAC` address on default `bridge` network (#17890)
|
||||
- Allow port-mapping only for endpoints created on docker run (#17858)
|
||||
- Fixed an endpoint delete issue with a possible stale sbox (#18102)
|
||||
|
||||
## Distribution
|
||||
|
||||
- Correct parent chain in v2 push when v1Compatibility files on the disk are inconsistent (#18047)
|
||||
|
||||
## 1.9.0 (2015-11-03)
|
||||
|
||||
## Runtime
|
||||
|
||||
@@ -58,9 +58,16 @@ RUN apt-get update && apt-get install -y \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
ubuntu-zfs \
|
||||
xfsprogs \
|
||||
libzfs-dev \
|
||||
--no-install-recommends
|
||||
|
||||
# clr
|
||||
RUN echo deb http://download.opensuse.org/repositories/home:/clearlinux:/preview/xUbuntu_15.04/ / > /etc/apt/sources.list.d/clear-containers-docker.list
|
||||
RUN apt-get --allow-unauthenticated update && apt-get install -y --force-yes \
|
||||
kvmtool\
|
||||
linux-container
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
|
||||
@@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
e2fsprogs \
|
||||
iptables \
|
||||
procps \
|
||||
xfsprogs \
|
||||
xz-utils \
|
||||
\
|
||||
aufs-tools \
|
||||
|
||||
+15
-8
@@ -61,14 +61,19 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
for _, name := range cmd.Args() {
|
||||
if *inspectType == "" || *inspectType == "container" {
|
||||
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
|
||||
if err != nil && *inspectType == "container" {
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
if err != nil {
|
||||
if err == errConnectionFailed {
|
||||
return err
|
||||
}
|
||||
if *inspectType == "container" {
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +81,9 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
||||
isImage = true
|
||||
if err != nil {
|
||||
if err == errConnectionFailed {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
if *inspectType == "" {
|
||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||
@@ -88,7 +96,6 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if tmpl == nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -33,6 +34,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
||||
if runtime.GOOS == "windows" {
|
||||
cli.in = os.Stdin
|
||||
}
|
||||
|
||||
serverAddress := registry.IndexServer
|
||||
if len(cmd.Args()) > 0 {
|
||||
serverAddress = cmd.Arg(0)
|
||||
|
||||
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
)
|
||||
|
||||
var validDrivers = map[string]bool{
|
||||
"json-file": true,
|
||||
"journald": true,
|
||||
}
|
||||
|
||||
// CmdLogs fetches the logs of a given container.
|
||||
//
|
||||
// docker logs [OPTIONS] CONTAINER
|
||||
@@ -36,6 +42,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !validDrivers[c.HostConfig.LogConfig.Type] {
|
||||
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type)
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
@@ -66,7 +66,9 @@ func (s *router) getContainersStats(ctx context.Context, w http.ResponseWriter,
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
out = w
|
||||
} else {
|
||||
out = ioutils.NewWriteFlusher(w)
|
||||
wf := ioutils.NewWriteFlusher(w)
|
||||
out = wf
|
||||
defer wf.Close()
|
||||
}
|
||||
|
||||
var closeNotifier <-chan bool
|
||||
@@ -122,11 +124,16 @@ func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r
|
||||
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
||||
}
|
||||
|
||||
outStream := ioutils.NewWriteFlusher(w)
|
||||
// write an empty chunk of data (this is to ensure that the
|
||||
// HTTP Response is sent immediately, even if the container has
|
||||
// not yet produced any data)
|
||||
outStream.Write(nil)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
|
||||
logsConfig := &daemon.ContainerLogsConfig{
|
||||
Follow: httputils.BoolValue(r, "follow"),
|
||||
@@ -135,7 +142,7 @@ func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r
|
||||
Tail: r.Form.Get("tail"),
|
||||
UseStdout: stdout,
|
||||
UseStderr: stderr,
|
||||
OutStream: outStream,
|
||||
OutStream: output,
|
||||
Stop: closeNotifier,
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r
|
||||
err error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
)
|
||||
defer output.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -188,6 +189,7 @@ func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *h
|
||||
|
||||
name := vars["name"]
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
imagePushConfig := &graph.ImagePushConfig{
|
||||
MetaHeaders: metaHeaders,
|
||||
AuthConfig: authConfig,
|
||||
@@ -218,6 +220,7 @@ func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *htt
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
var names []string
|
||||
if name, ok := vars["name"]; ok {
|
||||
names = []string{name}
|
||||
@@ -297,6 +300,7 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
sf := streamformatter.NewJSONStreamFormatter()
|
||||
errf := func(err error) error {
|
||||
// Do not write the error in the http output if it's still empty.
|
||||
|
||||
@@ -52,16 +52,6 @@ func (s *router) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Req
|
||||
return httputils.WriteJSON(w, http.StatusOK, info)
|
||||
}
|
||||
|
||||
func buildOutputEncoder(w http.ResponseWriter) *json.Encoder {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
outStream := ioutils.NewWriteFlusher(w)
|
||||
// Write an empty chunk of data.
|
||||
// This is to ensure that the HTTP status code is sent immediately,
|
||||
// so that it will not block the receiver.
|
||||
outStream.Write(nil)
|
||||
return json.NewEncoder(outStream)
|
||||
}
|
||||
|
||||
func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
@@ -87,7 +77,19 @@ func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
return err
|
||||
}
|
||||
|
||||
enc := buildOutputEncoder(w)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// This is to ensure that the HTTP status code is sent immediately,
|
||||
// so that it will not block the receiver.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
d := s.daemon
|
||||
es := d.EventsService
|
||||
current, l := es.Subscribe()
|
||||
|
||||
@@ -199,7 +199,11 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
|
||||
|
||||
epl := nw.Endpoints()
|
||||
for _, e := range epl {
|
||||
sb := e.Info().Sandbox()
|
||||
ei := e.Info()
|
||||
if ei == nil {
|
||||
continue
|
||||
}
|
||||
sb := ei.Sandbox()
|
||||
if sb == nil {
|
||||
continue
|
||||
}
|
||||
@@ -241,7 +245,12 @@ func buildEndpointResource(e libnetwork.Endpoint) types.EndpointResource {
|
||||
}
|
||||
|
||||
er.EndpointID = e.ID()
|
||||
if iface := e.Info().Iface(); iface != nil {
|
||||
ei := e.Info()
|
||||
if ei == nil {
|
||||
return er
|
||||
}
|
||||
|
||||
if iface := ei.Iface(); iface != nil {
|
||||
if mac := iface.MacAddress(); mac != nil {
|
||||
er.MacAddress = mac.String()
|
||||
}
|
||||
|
||||
+12
-1
@@ -33,7 +33,8 @@ type Context interface {
|
||||
Close() error
|
||||
// Stat returns an entry corresponding to path if any.
|
||||
// It is recommended to return an error if path was not found.
|
||||
Stat(path string) (FileInfo, error)
|
||||
// If path is a symlink it also returns the path to the target file.
|
||||
Stat(path string) (string, FileInfo, error)
|
||||
// Open opens path from the context and returns a readable stream of it.
|
||||
Open(path string) (io.ReadCloser, error)
|
||||
// Walk walks the tree of the context with the function passed to it.
|
||||
@@ -64,6 +65,8 @@ type PathFileInfo struct {
|
||||
os.FileInfo
|
||||
// FilePath holds the absolute path to the file.
|
||||
FilePath string
|
||||
// Name holds the basename for the file.
|
||||
FileName string
|
||||
}
|
||||
|
||||
// Path returns the absolute path to the file.
|
||||
@@ -71,6 +74,14 @@ func (fi PathFileInfo) Path() string {
|
||||
return fi.FilePath
|
||||
}
|
||||
|
||||
// Name returns the basename of the file.
|
||||
func (fi PathFileInfo) Name() string {
|
||||
if fi.FileName != "" {
|
||||
return fi.FileName
|
||||
}
|
||||
return fi.FileInfo.Name()
|
||||
}
|
||||
|
||||
// Hashed defines an extra method intended for implementations of os.FileInfo.
|
||||
type Hashed interface {
|
||||
// Hash returns the hash of a file.
|
||||
|
||||
@@ -366,7 +366,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
|
||||
|
||||
// Must be a dir or a file
|
||||
|
||||
fi, err := b.context.Stat(origPath)
|
||||
statPath, fi, err := b.context.Stat(origPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -383,11 +383,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
|
||||
hfi.SetHash("file:" + hfi.Hash())
|
||||
return copyInfos, nil
|
||||
}
|
||||
|
||||
// Must be a dir
|
||||
|
||||
var subfiles []string
|
||||
b.context.Walk(origPath, func(path string, info builder.FileInfo, err error) error {
|
||||
err = b.context.Walk(statPath, func(path string, info builder.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -395,6 +393,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
|
||||
subfiles = append(subfiles, info.(builder.Hashed).Hash())
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Strings(subfiles)
|
||||
hasher := sha256.New()
|
||||
@@ -604,9 +605,9 @@ func (b *Builder) readDockerfile() error {
|
||||
// back to 'Dockerfile' and use that in the error message.
|
||||
if b.DockerfileName == "" {
|
||||
b.DockerfileName = api.DefaultDockerfileName
|
||||
if _, err := b.context.Stat(b.DockerfileName); os.IsNotExist(err) {
|
||||
if _, _, err := b.context.Stat(b.DockerfileName); os.IsNotExist(err) {
|
||||
lowercase := strings.ToLower(b.DockerfileName)
|
||||
if _, err := b.context.Stat(lowercase); err == nil {
|
||||
if _, _, err := b.context.Stat(lowercase); err == nil {
|
||||
b.DockerfileName = lowercase
|
||||
}
|
||||
}
|
||||
|
||||
+28
-35
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
@@ -43,26 +42,32 @@ func (c *tarSumContext) Open(path string) (io.ReadCloser, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *tarSumContext) Stat(path string) (fi FileInfo, err error) {
|
||||
func (c *tarSumContext) Stat(path string) (string, FileInfo, error) {
|
||||
cleanpath, fullpath, err := c.normalize(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
st, err := os.Lstat(fullpath)
|
||||
if err != nil {
|
||||
return nil, convertPathError(err, cleanpath)
|
||||
return "", nil, convertPathError(err, cleanpath)
|
||||
}
|
||||
|
||||
fi = PathFileInfo{st, fullpath}
|
||||
// we set sum to path by default for the case where GetFile returns nil.
|
||||
// The usual case is if cleanpath is empty.
|
||||
rel, err := filepath.Rel(c.root, fullpath)
|
||||
if err != nil {
|
||||
return "", nil, convertPathError(err, cleanpath)
|
||||
}
|
||||
|
||||
// We set sum to path by default for the case where GetFile returns nil.
|
||||
// The usual case is if relative path is empty.
|
||||
sum := path
|
||||
if tsInfo := c.sums.GetFile(cleanpath); tsInfo != nil {
|
||||
// Use the checksum of the followed path(not the possible symlink) because
|
||||
// this is the file that is actually copied.
|
||||
if tsInfo := c.sums.GetFile(rel); tsInfo != nil {
|
||||
sum = tsInfo.Sum()
|
||||
}
|
||||
fi = &HashedFileInfo{fi, sum}
|
||||
return fi, nil
|
||||
fi := &HashedFileInfo{PathFileInfo{st, fullpath, filepath.Base(cleanpath)}, sum}
|
||||
return rel, fi, nil
|
||||
}
|
||||
|
||||
// MakeTarSumContext returns a build Context from a tar stream.
|
||||
@@ -114,7 +119,7 @@ func (c *tarSumContext) normalize(path string) (cleanpath, fullpath string, err
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullpath)
|
||||
}
|
||||
_, err = os.Stat(fullpath)
|
||||
_, err = os.Lstat(fullpath)
|
||||
if err != nil {
|
||||
return "", "", convertPathError(err, path)
|
||||
}
|
||||
@@ -122,38 +127,26 @@ func (c *tarSumContext) normalize(path string) (cleanpath, fullpath string, err
|
||||
}
|
||||
|
||||
func (c *tarSumContext) Walk(root string, walkFn WalkFunc) error {
|
||||
for _, tsInfo := range c.sums {
|
||||
path := tsInfo.Name()
|
||||
path, fullpath, err := c.normalize(path)
|
||||
root = filepath.Join(c.root, filepath.Join(string(filepath.Separator), root))
|
||||
return filepath.Walk(root, func(fullpath string, info os.FileInfo, err error) error {
|
||||
rel, err := filepath.Rel(c.root, fullpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Any file in the context that starts with the given path will be
|
||||
// picked up and its hashcode used. However, we'll exclude the
|
||||
// root dir itself. We do this for a coupel of reasons:
|
||||
// 1 - ADD/COPY will not copy the dir itself, just its children
|
||||
// so there's no reason to include it in the hash calc
|
||||
// 2 - the metadata on the dir will change when any child file
|
||||
// changes. This will lead to a miss in the cache check if that
|
||||
// child file is in the .dockerignore list.
|
||||
if rel, err := filepath.Rel(root, path); err != nil {
|
||||
return err
|
||||
} else if rel == "." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
continue
|
||||
if rel == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := os.Lstat(fullpath)
|
||||
if err != nil {
|
||||
return convertPathError(err, path)
|
||||
sum := rel
|
||||
if tsInfo := c.sums.GetFile(rel); tsInfo != nil {
|
||||
sum = tsInfo.Sum()
|
||||
}
|
||||
// TODO check context breakout?
|
||||
fi := &HashedFileInfo{PathFileInfo{info, fullpath}, tsInfo.Sum()}
|
||||
if err := walkFn(path, fi, nil); err != nil {
|
||||
fi := &HashedFileInfo{PathFileInfo{FileInfo: info, FilePath: fullpath}, sum}
|
||||
if err := walkFn(rel, fi, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *tarSumContext) Remove(path string) error {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"!
|
||||
#
|
||||
|
||||
FROM fedora:23
|
||||
|
||||
RUN dnf install -y @development-tools fedora-packager
|
||||
RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel selinux-policy selinux-policy-devel sqlite-devel tar
|
||||
|
||||
ENV GO_VERSION 1.5.1
|
||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
ENV AUTO_GOPATH 1
|
||||
ENV DOCKER_BUILDTAGS selinux
|
||||
@@ -42,6 +42,9 @@
|
||||
# options immediately following their corresponding long form.
|
||||
# This order should be applied to lists, alternatives and code blocks.
|
||||
|
||||
__docker_previous_extglob_setting=$(shopt -p extglob)
|
||||
shopt -s extglob
|
||||
|
||||
__docker_q() {
|
||||
docker ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@"
|
||||
}
|
||||
@@ -1367,6 +1370,7 @@ _docker_run() {
|
||||
--ulimit
|
||||
--user -u
|
||||
--uts
|
||||
--volume-driver
|
||||
--volumes-from
|
||||
--volume -v
|
||||
--workdir -w
|
||||
@@ -1511,6 +1515,10 @@ _docker_run() {
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--volume-driver)
|
||||
COMPREPLY=( $( compgen -W "local" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--volumes-from)
|
||||
__docker_containers_all
|
||||
return
|
||||
@@ -1864,4 +1872,7 @@ _docker() {
|
||||
return 0
|
||||
}
|
||||
|
||||
eval "$__docker_previous_extglob_setting"
|
||||
unset __docker_previous_extglob_setting
|
||||
|
||||
complete -F _docker docker
|
||||
|
||||
@@ -339,7 +339,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l restart -d 'Res
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l security-opt -d 'Security Options'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal 'Signal to kill a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal -d 'Signal to kill a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)'
|
||||
|
||||
@@ -334,7 +334,7 @@ __docker_volume_subcommand() {
|
||||
(create)
|
||||
_arguments \
|
||||
$opts_help \
|
||||
"($help -d --driver)"{-d,--driver=}"[Specify volume driver name]:Driver name: " \
|
||||
"($help -d --driver)"{-d,--driver=}"[Specify volume driver name]:Driver name:(local)" \
|
||||
"($help)--name=[Specify volume name]" \
|
||||
"($help)*"{-o,--opt=}"[Set driver specific options]:Driver option: " && ret=0
|
||||
;;
|
||||
@@ -444,6 +444,7 @@ __docker_subcommand() {
|
||||
"($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]"
|
||||
"($help -u --user)"{-u,--user=}"[Username or UID]:user:_users"
|
||||
"($help)*-v[Bind mount a volume]:volume: "
|
||||
"($help)--volume-driver=[Optional volume driver for the container]:volume driver:(local)"
|
||||
"($help)*--volumes-from=[Mount volumes from the specified container]:volume: "
|
||||
"($help -w --workdir)"{-w,--workdir=}"[Working directory inside the container]:directory:_directories"
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
var (
|
||||
defaultPidFile = "/var/run/docker.pid"
|
||||
defaultGraph = "/var/lib/docker"
|
||||
defaultExec = "native"
|
||||
defaultExec = "clr"
|
||||
)
|
||||
|
||||
// Config defines the configuration of a docker daemon.
|
||||
|
||||
+23
-4
@@ -153,7 +153,28 @@ func (container *Container) readHostConfig() error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return json.NewDecoder(f).Decode(&container.hostConfig)
|
||||
if err := json.NewDecoder(f).Decode(&container.hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure the dns fields are never nil.
|
||||
// New containers don't ever have those fields nil,
|
||||
// but pre created containers can still have those nil values.
|
||||
// See https://github.com/docker/docker/pull/17779
|
||||
// for a more detailed explanation on why we don't want that.
|
||||
if container.hostConfig.DNS == nil {
|
||||
container.hostConfig.DNS = make([]string, 0)
|
||||
}
|
||||
|
||||
if container.hostConfig.DNSSearch == nil {
|
||||
container.hostConfig.DNSSearch = make([]string, 0)
|
||||
}
|
||||
|
||||
if container.hostConfig.DNSOptions == nil {
|
||||
container.hostConfig.DNSOptions = make([]string, 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) writeHostConfig() error {
|
||||
@@ -333,9 +354,7 @@ func (streamConfig *streamConfig) StderrPipe() io.ReadCloser {
|
||||
func (container *Container) cleanup() {
|
||||
container.releaseNetwork()
|
||||
|
||||
if err := container.unmountIpcMounts(); err != nil {
|
||||
logrus.Errorf("%s: Failed to umount ipc filesystems: %v", container.ID, err)
|
||||
}
|
||||
container.unmountIpcMounts(detachMounted)
|
||||
|
||||
if err := container.Unmount(); err != nil {
|
||||
logrus.Errorf("%s: Failed to umount filesystem: %v", container.ID, err)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
@@ -64,3 +69,68 @@ func TestContainerStopSignal(t *testing.T) {
|
||||
t.Fatalf("Expected 9, got %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerInitDNS(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-container-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
containerID := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e"
|
||||
containerPath := filepath.Join(tmp, containerID)
|
||||
if err := os.MkdirAll(containerPath, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0,
|
||||
"Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"},
|
||||
"ID":"d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e","Created":"2015-05-26T16:48:53.7987917Z","Path":"top",
|
||||
"Args":[],"Config":{"Hostname":"d59df5276e7b","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"Cpuset":"",
|
||||
"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":true,"OpenStdin":true,
|
||||
"StdinOnce":false,"Env":null,"Cmd":["top"],"Image":"ubuntu:latest","Volumes":null,"WorkingDir":"","Entrypoint":null,
|
||||
"NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{}},"Image":"07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95",
|
||||
"NetworkSettings":{"IPAddress":"172.17.0.1","IPPrefixLen":16,"MacAddress":"02:42:ac:11:00:01","LinkLocalIPv6Address":"fe80::42:acff:fe11:1",
|
||||
"LinkLocalIPv6PrefixLen":64,"GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"Gateway":"172.17.42.1","IPv6Gateway":"","Bridge":"docker0","Ports":{}},
|
||||
"ResolvConfPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/resolv.conf",
|
||||
"HostnamePath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hostname",
|
||||
"HostsPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hosts",
|
||||
"LogPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e-json.log",
|
||||
"Name":"/ubuntu","Driver":"aufs","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0,
|
||||
"UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
|
||||
|
||||
if err = ioutil.WriteFile(filepath.Join(containerPath, "config.json"), []byte(config), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hostConfig := `{"Binds":[],"ContainerIDFile":"","Memory":0,"MemorySwap":0,"CpuShares":0,"CpusetCpus":"",
|
||||
"Privileged":false,"PortBindings":{},"Links":null,"PublishAllPorts":false,"Dns":null,"DnsOptions":null,"DnsSearch":null,"ExtraHosts":null,"VolumesFrom":null,
|
||||
"Devices":[],"NetworkMode":"bridge","IpcMode":"","PidMode":"","CapAdd":null,"CapDrop":null,"RestartPolicy":{"Name":"no","MaximumRetryCount":0},
|
||||
"SecurityOpt":null,"ReadonlyRootfs":false,"Ulimits":null,"LogConfig":{"Type":"","Config":null},"CgroupParent":""}`
|
||||
if err = ioutil.WriteFile(filepath.Join(containerPath, "hostconfig.json"), []byte(hostConfig), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer volumedrivers.Unregister(volume.DefaultDriverName)
|
||||
|
||||
c, err := daemon.load(containerID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.hostConfig.DNS == nil {
|
||||
t.Fatal("Expected container DNS to not be nil")
|
||||
}
|
||||
|
||||
if c.hostConfig.DNSSearch == nil {
|
||||
t.Fatal("Expected container DNSSearch to not be nil")
|
||||
}
|
||||
|
||||
if c.hostConfig.DNSOptions == nil {
|
||||
t.Fatal("Expected container DNSOptions to not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
+51
-29
@@ -229,10 +229,10 @@ func populateCommand(c *Container, env []string) error {
|
||||
ipc.HostIpc = c.hostConfig.IpcMode.IsHost()
|
||||
if ipc.HostIpc {
|
||||
if _, err := os.Stat("/dev/shm"); err != nil {
|
||||
return fmt.Errorf("/dev/shm is not mounted, but must be for --host=ipc")
|
||||
return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host")
|
||||
}
|
||||
if _, err := os.Stat("/dev/mqueue"); err != nil {
|
||||
return fmt.Errorf("/dev/mqueue is not mounted, but must be for --host=ipc")
|
||||
return fmt.Errorf("/dev/mqueue is not mounted, but must be for --ipc=host")
|
||||
}
|
||||
c.ShmPath = "/dev/shm"
|
||||
c.MqueuePath = "/dev/mqueue"
|
||||
@@ -621,7 +621,9 @@ func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint, networkSett
|
||||
return networkSettings, nil
|
||||
}
|
||||
|
||||
networkSettings.Ports = nat.PortMap{}
|
||||
if networkSettings.Ports == nil {
|
||||
networkSettings.Ports = nat.PortMap{}
|
||||
}
|
||||
|
||||
if expData, ok := driverInfo[netlabel.ExposedPorts]; ok {
|
||||
if exposedPorts, ok := expData.([]types.TransportPort); ok {
|
||||
@@ -723,6 +725,10 @@ func (container *Container) updateNetworkSettings(n libnetwork.Network) error {
|
||||
container.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)}
|
||||
}
|
||||
|
||||
if !container.hostConfig.NetworkMode.IsHost() && runconfig.NetworkMode(n.Type()).IsHost() {
|
||||
return runconfig.ErrConflictHostNetwork
|
||||
}
|
||||
|
||||
for s := range container.NetworkSettings.Networks {
|
||||
sn, err := container.daemon.FindNetwork(s)
|
||||
if err != nil {
|
||||
@@ -816,6 +822,17 @@ func (container *Container) buildCreateEndpointOptions(n libnetwork.Network) ([]
|
||||
createOptions []libnetwork.EndpointOption
|
||||
)
|
||||
|
||||
if n.Name() == "bridge" || container.NetworkSettings.IsAnonymousEndpoint {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
|
||||
}
|
||||
|
||||
// Other configs are applicable only for the endpoint in the network
|
||||
// to which container was connected to on docker run.
|
||||
if n.Name() != container.hostConfig.NetworkMode.NetworkName() &&
|
||||
!(n.Name() == "bridge" && container.hostConfig.NetworkMode.IsDefault()) {
|
||||
return createOptions, nil
|
||||
}
|
||||
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
@@ -885,10 +902,6 @@ func (container *Container) buildCreateEndpointOptions(n libnetwork.Network) ([]
|
||||
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption))
|
||||
}
|
||||
|
||||
if n.Name() == "bridge" || container.NetworkSettings.IsAnonymousEndpoint {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
|
||||
}
|
||||
|
||||
return createOptions, nil
|
||||
}
|
||||
|
||||
@@ -927,6 +940,13 @@ func (container *Container) allocateNetwork() error {
|
||||
if mode.IsDefault() {
|
||||
networkName = controller.Config().Daemon.DefaultNetwork
|
||||
}
|
||||
if mode.IsUserDefined() {
|
||||
n, err := container.daemon.FindNetwork(networkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networkName = n.Name()
|
||||
}
|
||||
container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
|
||||
container.NetworkSettings.Networks[networkName] = new(network.EndpointSettings)
|
||||
updateSettings = true
|
||||
@@ -953,7 +973,7 @@ func (container *Container) getNetworkSandbox() libnetwork.Sandbox {
|
||||
return sb
|
||||
}
|
||||
|
||||
// ConnectToNetwork connects a container to a netork
|
||||
// ConnectToNetwork connects a container to a network
|
||||
func (container *Container) ConnectToNetwork(idOrName string) error {
|
||||
if !container.Running {
|
||||
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
|
||||
@@ -967,9 +987,7 @@ func (container *Container) ConnectToNetwork(idOrName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) connectToNetwork(idOrName string, updateSettings bool) error {
|
||||
var err error
|
||||
|
||||
func (container *Container) connectToNetwork(idOrName string, updateSettings bool) (err error) {
|
||||
if container.hostConfig.NetworkMode.IsContainer() {
|
||||
return runconfig.ErrConflictSharedNetwork
|
||||
}
|
||||
@@ -1196,6 +1214,10 @@ func (container *Container) DisconnectFromNetwork(n libnetwork.Network) error {
|
||||
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
|
||||
}
|
||||
|
||||
if container.hostConfig.NetworkMode.IsHost() && runconfig.NetworkMode(n.Type()).IsHost() {
|
||||
return runconfig.ErrConflictHostNetwork
|
||||
}
|
||||
|
||||
if err := container.disconnectFromNetwork(n); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1213,7 +1235,11 @@ func (container *Container) disconnectFromNetwork(n libnetwork.Network) error {
|
||||
)
|
||||
|
||||
s := func(current libnetwork.Endpoint) bool {
|
||||
if sb := current.Info().Sandbox(); sb != nil {
|
||||
epInfo := current.Info()
|
||||
if epInfo == nil {
|
||||
return false
|
||||
}
|
||||
if sb := epInfo.Sandbox(); sb != nil {
|
||||
if sb.ContainerID() == container.ID {
|
||||
ep = current
|
||||
sbox = sb
|
||||
@@ -1456,22 +1482,21 @@ func (container *Container) setupIpcDirs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) unmountIpcMounts() error {
|
||||
func (container *Container) unmountIpcMounts(unmount func(pth string) error) {
|
||||
if container.hostConfig.IpcMode.IsContainer() || container.hostConfig.IpcMode.IsHost() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
var errors []string
|
||||
var warnings []string
|
||||
|
||||
if !container.hasMountFor("/dev/shm") {
|
||||
shmPath, err := container.shmPath()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
errors = append(errors, err.Error())
|
||||
} else {
|
||||
if err := detachMounted(shmPath); err != nil {
|
||||
logrus.Errorf("failed to umount %s: %v", shmPath, err)
|
||||
errors = append(errors, err.Error())
|
||||
warnings = append(warnings, err.Error())
|
||||
} else if shmPath != "" {
|
||||
if err := unmount(shmPath); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", shmPath, err))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1481,20 +1506,17 @@ func (container *Container) unmountIpcMounts() error {
|
||||
mqueuePath, err := container.mqueuePath()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
errors = append(errors, err.Error())
|
||||
} else {
|
||||
if err := detachMounted(mqueuePath); err != nil {
|
||||
logrus.Errorf("failed to umount %s: %v", mqueuePath, err)
|
||||
errors = append(errors, err.Error())
|
||||
warnings = append(warnings, err.Error())
|
||||
} else if mqueuePath != "" {
|
||||
if err := unmount(mqueuePath); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", mqueuePath, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("failed to cleanup ipc mounts:\n%v", strings.Join(errors, "\n"))
|
||||
if len(warnings) > 0 {
|
||||
logrus.Warnf("failed to cleanup ipc mounts:\n%v", strings.Join(warnings, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) ipcMounts() []execdriver.Mount {
|
||||
|
||||
@@ -183,11 +183,14 @@ func (container *Container) removeMountPoints(_ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) setupIpcDirs() error {
|
||||
func (container *Container) unmountIpcMounts(unmount func(pth string) error) {
|
||||
}
|
||||
|
||||
func detachMounted(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) unmountIpcMounts() error {
|
||||
func (container *Container) setupIpcDirs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/docker/docker/pkg/graphdb"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/parsers/filters"
|
||||
@@ -222,9 +223,8 @@ func (daemon *Daemon) Register(container *Container) error {
|
||||
}
|
||||
daemon.execDriver.Terminate(cmd)
|
||||
|
||||
if err := container.unmountIpcMounts(); err != nil {
|
||||
logrus.Errorf("%s: Failed to umount ipc filesystems: %v", container.ID, err)
|
||||
}
|
||||
container.unmountIpcMounts(mount.Unmount)
|
||||
|
||||
if err := container.Unmount(); err != nil {
|
||||
logrus.Debugf("unmount error %s", err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
)
|
||||
|
||||
// cleanupMounts umounts shm/mqueue mounts for old containers
|
||||
@@ -20,7 +21,7 @@ func (daemon *Daemon) cleanupMounts() error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return daemon.cleanupMountsFromReader(f, detachMounted)
|
||||
return daemon.cleanupMountsFromReader(f, mount.Unmount)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(target string) error) error {
|
||||
|
||||
+30
-5
@@ -182,7 +182,7 @@ func TestLoadWithVolume(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonForVolumesTest(tmp)
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -270,7 +270,7 @@ func TestLoadWithBindMount(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonForVolumesTest(tmp)
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -361,7 +361,7 @@ func TestLoadWithVolume17RC(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonForVolumesTest(tmp)
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -466,7 +466,7 @@ func TestRemoveLocalVolumesFollowingSymlinks(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonForVolumesTest(tmp)
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -502,7 +502,7 @@ func TestRemoveLocalVolumesFollowingSymlinks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
|
||||
func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
@@ -549,3 +549,28 @@ func TestParseSecurityOpt(t *testing.T) {
|
||||
t.Fatal("Expected parseSecurityOpt error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOptions(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
dconfigCorrect := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
DefaultNetwork: "netPlugin:mynet:dev",
|
||||
ClusterStore: "consul://localhost:8500",
|
||||
ClusterAdvertise: "192.168.0.1:8000",
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := daemon.networkOptions(dconfigCorrect); err != nil {
|
||||
t.Fatalf("Expect networkOptions sucess, got error: %v", err)
|
||||
}
|
||||
|
||||
dconfigWrong := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "consul://localhost:8500://test://bbb",
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := daemon.networkOptions(dconfigWrong); err == nil {
|
||||
t.Fatalf("Expected networkOptions error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
+18
-4
@@ -332,11 +332,11 @@ func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error)
|
||||
|
||||
if strings.TrimSpace(dconfig.ClusterStore) != "" {
|
||||
kv := strings.Split(dconfig.ClusterStore, "://")
|
||||
if len(kv) < 2 {
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("kv store daemon config must be of the form KV-PROVIDER://KV-URL")
|
||||
}
|
||||
options = append(options, nwconfig.OptionKVProvider(kv[0]))
|
||||
options = append(options, nwconfig.OptionKVProviderURL(strings.Join(kv[1:], "://")))
|
||||
options = append(options, nwconfig.OptionKVProviderURL(kv[1]))
|
||||
}
|
||||
if len(dconfig.ClusterOpts) > 0 {
|
||||
options = append(options, nwconfig.OptionKVOpts(dconfig.ClusterOpts))
|
||||
@@ -458,12 +458,25 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
|
||||
ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.Bridge.DefaultGatewayIPv4.String()
|
||||
}
|
||||
|
||||
var ipamV6Conf *libnetwork.IpamConf
|
||||
var (
|
||||
ipamV6Conf *libnetwork.IpamConf
|
||||
deferIPv6Alloc bool
|
||||
)
|
||||
if config.Bridge.FixedCIDRv6 != "" {
|
||||
_, fCIDRv6, err := net.ParseCIDR(config.Bridge.FixedCIDRv6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has
|
||||
// at least 48 host bits, we need to guarantee the current behavior where the containers'
|
||||
// IPv6 addresses will be constructed based on the containers' interface MAC address.
|
||||
// We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints
|
||||
// on this network until after the driver has created the endpoint and returned the
|
||||
// constructed address. Libnetwork will then reserve this address with the ipam driver.
|
||||
ones, _ := fCIDRv6.Mask.Size()
|
||||
deferIPv6Alloc = ones <= 80
|
||||
|
||||
if ipamV6Conf == nil {
|
||||
ipamV6Conf = &libnetwork.IpamConf{}
|
||||
}
|
||||
@@ -488,7 +501,8 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
|
||||
netlabel.GenericData: netOption,
|
||||
netlabel.EnableIPv6: config.Bridge.EnableIPv6,
|
||||
}),
|
||||
libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf))
|
||||
libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf),
|
||||
libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating default \"bridge\" network: %v", err)
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo,
|
||||
|
||||
// only needed for fixPermissions, but might as well put it before CopyFileWithTar
|
||||
if destExists && destStat.IsDir() {
|
||||
destPath = filepath.Join(destPath, filepath.Base(srcPath))
|
||||
destPath = filepath.Join(destPath, src.Name())
|
||||
}
|
||||
|
||||
if err := system.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
|
||||
|
||||
@@ -0,0 +1,964 @@
|
||||
// +build linux
|
||||
|
||||
package clr
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
sysinfo "github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/kr/pty"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
const (
|
||||
// Clear Linux for Intel(R) Architecture
|
||||
driverName = "clr"
|
||||
|
||||
envVarPrefix = "CLR_"
|
||||
|
||||
// variable that allows clrURL to be modified
|
||||
clrEnvURL = envVarPrefix + "DOWNLOAD_URL"
|
||||
|
||||
// Command used for lkvm control
|
||||
lkvmName = "lkvm"
|
||||
|
||||
// default value for clrURL
|
||||
defaultClrURL = "https://download.clearlinux.org"
|
||||
|
||||
// upstream latest release file (found below clrURL)
|
||||
latestFile = "latest"
|
||||
|
||||
// local "latest" information
|
||||
clrFile = "latest"
|
||||
|
||||
// ASCII file containing a checksums for the downloaded image.
|
||||
clrChecksumFile = "SHA512SUMS"
|
||||
|
||||
// clr kernel (not bzimage)
|
||||
clrKernel = "/usr/lib/kernel/vmlinux.container"
|
||||
)
|
||||
|
||||
var (
|
||||
// upstream base URL
|
||||
clrURL = defaultClrURL
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
root string // root path for the driver to use
|
||||
libPath string
|
||||
initPath string
|
||||
version string
|
||||
apparmor bool
|
||||
sharedRoot bool
|
||||
activeContainers map[string]*activeContainer
|
||||
machineMemory int64
|
||||
containerPid int
|
||||
sync.Mutex
|
||||
ipaddr string
|
||||
gateway string
|
||||
macaddr string
|
||||
}
|
||||
|
||||
type activeContainer struct {
|
||||
container *configs.Config
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
type ProgressReader struct {
|
||||
// The real reader
|
||||
io.Reader
|
||||
|
||||
// Total size of download
|
||||
total uint64
|
||||
|
||||
// Bytes received
|
||||
bytes uint64
|
||||
|
||||
// Rate-limiting counter
|
||||
lastShown int
|
||||
|
||||
rawURL string
|
||||
}
|
||||
|
||||
// Read implements the Reader interface by calling the _real_ reader
|
||||
// and periodically displaying progress information.
|
||||
func (r *ProgressReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
// A true error occured
|
||||
return n, err
|
||||
}
|
||||
|
||||
r.bytes += uint64(n)
|
||||
|
||||
if r.total == 0 {
|
||||
// http header did not provide size
|
||||
logrus.Debugf("Read %v bytes from %v", r.bytes, r.rawURL)
|
||||
} else {
|
||||
percent := (float64(r.bytes) / float64(r.total)) * 100
|
||||
|
||||
// rate-limit to only display messages for every 10% downloaded
|
||||
next := int(percent / 10)
|
||||
|
||||
if r.lastShown == -1 || next > r.lastShown {
|
||||
logrus.Debugf("Read %v of %v bytes from %v (%2.2f%%)", r.bytes, r.total, r.rawURL, percent)
|
||||
r.lastShown = next
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func getTapIf(c *execdriver.Command) string {
|
||||
return fmt.Sprintf("tb-%s", c.ID[:12])
|
||||
}
|
||||
|
||||
// getClrVersion reads the latest image version from the
|
||||
// locally-downloaded "latest" file.
|
||||
func getClrVersion(libPath string) string {
|
||||
txt, err := ioutil.ReadFile(path.Join(libPath, clrFile))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Split(string(txt), "\n")[0]
|
||||
}
|
||||
|
||||
func getURL(rawURL, outfile string) error {
|
||||
|
||||
_url, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(outfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
// Determine size of download by consulting the headers
|
||||
response, err := client.Head(rawURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lenStr := response.Header.Get("Content-Length")
|
||||
dataLen, _ := strconv.Atoi(lenStr)
|
||||
|
||||
request, err := http.NewRequest("GET", _url.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err = client.Do(request)
|
||||
defer response.Body.Close()
|
||||
|
||||
reader := &ProgressReader{
|
||||
Reader: response.Body,
|
||||
total: uint64(dataLen),
|
||||
lastShown: -1,
|
||||
rawURL: rawURL,
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, reader)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// fetchLatest downloads the "latest" file which contains the version
|
||||
// number of the latest image.
|
||||
func fetchLatest(libPath string) error {
|
||||
file := path.Join(libPath, clrFile)
|
||||
return getURL(clrURL+"/"+latestFile, file)
|
||||
}
|
||||
|
||||
// NewDriver creates a new clear linux execution driver.
|
||||
func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meminfo, err := sysinfo.ReadMemInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version, err := prepareClr(libPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &driver{
|
||||
apparmor: apparmor,
|
||||
root: root,
|
||||
libPath: libPath,
|
||||
initPath: initPath,
|
||||
version: version,
|
||||
sharedRoot: false,
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
// FIXME:
|
||||
machineMemory: meminfo.MemTotal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fetchImage downloads the latest image and verifies its checksum.
|
||||
func fetchImage(version, outfile, outpath string) error {
|
||||
|
||||
url := fmt.Sprintf("%s/releases/%s/clear/%s", clrURL, version, outfile)
|
||||
|
||||
logrus.Debugf("Fetching clr version: %s, %s", version, outpath)
|
||||
|
||||
return getURL(url, outpath)
|
||||
}
|
||||
|
||||
// uncompressImage handles decompressing the image specified by path.
|
||||
func uncompressImage(path string) error {
|
||||
// decompress the file
|
||||
cmd := exec.Command("unxz", path)
|
||||
cmd.Dir = filepath.Dir(path)
|
||||
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Unable to extract image %s: %s", path, output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChecksumLine reads the file specified and returns the checksum for
|
||||
// the filename specified.
|
||||
func getChecksumLine(file string, filename string) string {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasSuffix(line, filename) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 2 {
|
||||
return fields[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getChecksum downloads the checksum file for the image version specified,
|
||||
// storing it under libPath, then extracts the checksum corresponding to
|
||||
// imageFile and returns it.
|
||||
func getChecksum(version string, libPath string, imageFile string) (string, error) {
|
||||
outfile := path.Join(libPath, clrChecksumFile)
|
||||
checkurl := fmt.Sprintf("%s/releases/%s/clear/%s", clrURL, version, clrChecksumFile)
|
||||
|
||||
err := getURL(checkurl, outfile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Now, extract the checksum from the downloaded file.
|
||||
return getChecksumLine(outfile, imageFile), nil
|
||||
}
|
||||
|
||||
func verifyChecksum(fullPath, expectedChecksum string) error {
|
||||
|
||||
if expectedChecksum == "" {
|
||||
return errors.New("blank expectedChecksum")
|
||||
}
|
||||
if fullPath == "" {
|
||||
return errors.New("blank path")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
calculatedChecksum := fmt.Sprintf("%x", sha512.Sum512(data))
|
||||
|
||||
if calculatedChecksum != expectedChecksum {
|
||||
return fmt.Errorf("Checksum mismatch: %v %v", calculatedChecksum, expectedChecksum)
|
||||
}
|
||||
|
||||
logrus.Debugf("Checksum for %v correct: %v", fullPath, expectedChecksum)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// alreadyDownloaded determines if the uncompressed version of the file
|
||||
// specified by outpath already exists and seems sane.
|
||||
func alreadyDownloaded(outpath string) bool {
|
||||
// strip off the (first) extension
|
||||
base := path.Base(outpath)
|
||||
extension := filepath.Ext(base)
|
||||
|
||||
uncompressed := strings.TrimSuffix(outpath, extension)
|
||||
|
||||
st, err := os.Stat(uncompressed)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if st.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
if st.Size() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// prepareClr handles downloading and checking clr images, returning the
|
||||
// version of clr that will be used.
|
||||
func prepareClr(libPath string) (string, error) {
|
||||
logrus.Debugf("%s preparing environment", driverName)
|
||||
|
||||
tmp := os.Getenv(clrEnvURL)
|
||||
if tmp != "" {
|
||||
clrURL = tmp
|
||||
logrus.Debugf("%s using alternate download URL: %s", driverName, clrURL)
|
||||
}
|
||||
|
||||
var version = getClrVersion(libPath)
|
||||
|
||||
err := fetchLatest(libPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nversion := getClrVersion(libPath)
|
||||
if nversion == "" {
|
||||
return "", errors.New("unable to determine latest clr version")
|
||||
}
|
||||
|
||||
outfile := fmt.Sprintf("clear-%s-containers.img.xz", nversion)
|
||||
outpath := path.Join(libPath, outfile)
|
||||
|
||||
// If the download originally failed, there may be a partial .xz
|
||||
// file. However, this will be ignored and a new download triggered
|
||||
// since we only only check for the uncompressed file (which would
|
||||
// only exist if the download was successful).
|
||||
if alreadyDownloaded(outpath) && nversion == version {
|
||||
logrus.Debugf("Using clr version: %s", nversion)
|
||||
return nversion, nil
|
||||
}
|
||||
|
||||
// Determine the expected checksum for the file about to be
|
||||
// downloaded.
|
||||
checksum, err := getChecksum(nversion, libPath, outfile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if nversion != version && version != "" {
|
||||
logrus.Debugf("Updating to clr version: %s", nversion)
|
||||
} else if version == "" {
|
||||
logrus.Debugf("Installing clr version: %s", nversion)
|
||||
}
|
||||
|
||||
err = fetchImage(nversion, outfile, outpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = verifyChecksum(outpath, checksum); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = uncompressImage(outpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nversion, nil
|
||||
}
|
||||
|
||||
func (d *driver) Name() string {
|
||||
return fmt.Sprintf("%s-%s", driverName, d.version)
|
||||
}
|
||||
|
||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
)
|
||||
|
||||
container, err := d.createContainer(c)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
memoryMiB := c.HostConfig.Memory
|
||||
if memoryMiB == 0 {
|
||||
memoryMiB = 1024
|
||||
} else {
|
||||
// docker passes the value as bytes
|
||||
memoryMiB = memoryMiB / int64(math.Pow(2, 20))
|
||||
}
|
||||
|
||||
workingDirVar := fmt.Sprintf("%s%s=%q", envVarPrefix, "WORKINGDIR", c.WorkingDir)
|
||||
c.ProcessConfig.Cmd.Env = append(c.ProcessConfig.Cmd.Env, workingDirVar)
|
||||
|
||||
userVar := fmt.Sprintf("%s%s=%q", envVarPrefix, "USER", c.ProcessConfig.User)
|
||||
c.ProcessConfig.Cmd.Env = append(c.ProcessConfig.Cmd.Env, userVar)
|
||||
|
||||
if err := d.setupNetwork(c); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
if c.ProcessConfig.Tty {
|
||||
term, err = NewTtyConsole(&c.ProcessConfig, pipes)
|
||||
} else {
|
||||
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
|
||||
}
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
c.ProcessConfig.Terminal = term
|
||||
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = &activeContainer{
|
||||
container: container,
|
||||
cmd: &c.ProcessConfig.Cmd,
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
if err := d.generateEnvConfig(c); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
if err := d.generateDockerInit(c); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
for _, m := range c.Mounts {
|
||||
dest := path.Join(c.Rootfs, m.Destination)
|
||||
|
||||
if m.Destination == "/etc/hostname" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !pathExists(m.Source) {
|
||||
continue
|
||||
}
|
||||
|
||||
opts := "bind"
|
||||
|
||||
if m.Private {
|
||||
opts = opts + ",rprivate"
|
||||
}
|
||||
if m.Slave {
|
||||
opts = opts + ",rslave"
|
||||
}
|
||||
|
||||
// This may look racy, but it isn't since the VM isn't
|
||||
// running yet.
|
||||
//
|
||||
// The check is necessary to handle bind mounting of
|
||||
// regular files correctly since without it we may be
|
||||
// attempting to create a directory where there already
|
||||
// exists a normal file.
|
||||
if !pathExists(dest) {
|
||||
if err := os.MkdirAll(dest, 0750); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mount.Mount(m.Source, dest, "", opts); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
if !m.Writable {
|
||||
if err := mount.Mount("", dest, "", "bind,remount,ro"); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
}
|
||||
defer mount.Unmount(dest)
|
||||
}
|
||||
|
||||
var args []string
|
||||
// various things for lkvm
|
||||
ifname := getTapIf(c)
|
||||
// FIXME: Should be real hostname from like process/container struct
|
||||
hostname := c.ID[0:12]
|
||||
img := fmt.Sprintf("%s/clear-%s-containers.img", d.libPath, d.version)
|
||||
memory := fmt.Sprintf("%d", memoryMiB)
|
||||
// FIXME: Locked cores to 6 ?
|
||||
cores := fmt.Sprintf("%d", 6)
|
||||
|
||||
args = append(args, c.ProcessConfig.Entrypoint)
|
||||
args = append(args, c.ProcessConfig.Arguments...)
|
||||
|
||||
rootParams := fmt.Sprintf("root=/dev/plkvm0p1 rootfstype=ext4 rootflags=dax,data=ordered "+
|
||||
"init=/usr/lib/systemd/systemd systemd.unit=container.target rw tsc=reliable "+
|
||||
"systemd.show_status=false "+
|
||||
"no_timer_check rcupdate.rcu_expedited=1 console=hvc0 quiet ip=%s::%s::%s::off",
|
||||
d.ipaddr, d.gateway, hostname)
|
||||
|
||||
params := []string{
|
||||
lkvmName, "run", "-c", cores, "-m", memory,
|
||||
"--name", c.ID, "--console", "virtio",
|
||||
"--kernel", clrKernel,
|
||||
"--params", rootParams,
|
||||
"--shmem", fmt.Sprintf("0x200000000:0:file=%s:private", img),
|
||||
"--network", fmt.Sprintf("mode=tap,script=none,tapif=%s,guest_mac=%s", ifname, d.macaddr),
|
||||
"--9p", fmt.Sprintf("%s,rootfs", c.Rootfs),
|
||||
}
|
||||
|
||||
logrus.Debugf("%s params %s", driverName, params)
|
||||
var (
|
||||
name = params[0]
|
||||
arg = params[1:]
|
||||
)
|
||||
aname, err := exec.LookPath(name)
|
||||
if err != nil {
|
||||
aname = name
|
||||
}
|
||||
c.ProcessConfig.Path = aname
|
||||
c.ProcessConfig.Args = append([]string{name}, arg...)
|
||||
c.ProcessConfig.Env = []string{fmt.Sprintf("HOME=%s", d.root)}
|
||||
|
||||
// Start the container. Since it runs synchronously, we don't Wait()
|
||||
// for it since we need to check the status to determine if it did
|
||||
// actually start successfully.
|
||||
if err := c.ProcessConfig.Start(); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
var (
|
||||
waitErr error
|
||||
waitLock = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
if err := c.ProcessConfig.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0
|
||||
waitErr = err
|
||||
}
|
||||
}
|
||||
close(waitLock)
|
||||
}()
|
||||
|
||||
// FIXME: need to create state.json for Stats() to work.
|
||||
pid := c.ProcessConfig.Process.Pid
|
||||
c.ContainerPid = pid
|
||||
d.containerPid = pid
|
||||
|
||||
if hooks.Start != nil {
|
||||
logrus.Debugf("Invoking startCallback")
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, pid, chOOM)
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
oomKill := false
|
||||
|
||||
// Wait for the VM to shutdown
|
||||
<-waitLock
|
||||
exitCode := getExitCode(c)
|
||||
|
||||
cExitStatus, cerr := d.cleanupVM(c)
|
||||
|
||||
if cerr != nil {
|
||||
waitErr = cerr
|
||||
exitCode = cExitStatus
|
||||
}
|
||||
|
||||
// check oom error
|
||||
if oomKill {
|
||||
exitCode = 137
|
||||
}
|
||||
|
||||
return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: false}, waitErr
|
||||
}
|
||||
|
||||
func pathExists(path string) bool {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func pathExecutable(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
mode := s.Mode()
|
||||
if mode&0111 != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *driver) cleanupVM(c *execdriver.Command) (exitStatus int, err error) {
|
||||
cmd := exec.Command("ip", "tuntap", "del", "dev", getTapIf(c), "mode", "tap")
|
||||
var output []byte
|
||||
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
waitStatus := exitError.Sys().(syscall.WaitStatus)
|
||||
exitStatus = waitStatus.ExitStatus()
|
||||
}
|
||||
logrus.Debugf("teardown failed for vm %s: %s (%s)", c.ID, string(output), err.Error())
|
||||
}
|
||||
|
||||
// doesn't matter if this fails
|
||||
// lkvm could have removed it, and stale sockets are not fatal
|
||||
_ = os.Remove(fmt.Sprintf("%s/.lkvm/%s.sock", d.root, c.ID))
|
||||
|
||||
return exitStatus, err
|
||||
}
|
||||
|
||||
// createContainer populates and configures the container type with the
|
||||
// data provided by the execdriver.Command
|
||||
func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
|
||||
return execdriver.InitContainer(c), nil
|
||||
}
|
||||
|
||||
/// Return the exit code of the process
|
||||
// if the process has not exited -1 will be returned
|
||||
func getExitCode(c *execdriver.Command) int {
|
||||
if c.ProcessConfig.ProcessState == nil {
|
||||
return -1
|
||||
}
|
||||
return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
|
||||
func (d *driver) lkvmCommand(c *execdriver.Command, arg string) ([]byte, error) {
|
||||
args := append([]string{lkvmName}, arg)
|
||||
if c != nil {
|
||||
args = append(args, "--name", c.ID)
|
||||
}
|
||||
cmd := exec.Command(lkvmName, args...)
|
||||
cmd.Env = []string{fmt.Sprintf("HOME=%s", d.root)}
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
// Kill sends a signal to workload
|
||||
func (d *driver) Kill(c *execdriver.Command, sig int) error {
|
||||
// Not supported
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
_, err := d.lkvmCommand(c, "pause")
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
_, err := d.lkvmCommand(c, "resume")
|
||||
return err
|
||||
}
|
||||
|
||||
// Terminate forcibly stops a container
|
||||
func (d *driver) Terminate(c *execdriver.Command) error {
|
||||
_, err := d.lkvmCommand(c, "stop")
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) containerDir(containerID string) string {
|
||||
return path.Join(d.libPath, "containers", containerID)
|
||||
}
|
||||
|
||||
// isDigit returns true if s can be represented as an integer
|
||||
func isDigit(s string) bool {
|
||||
if _, err := strconv.Atoi(s); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *driver) getInfo(id string) ([]byte, error) {
|
||||
output, err := d.lkvmCommand(nil, "list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, line := range strings.Split(string(output), "\n") {
|
||||
if i < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(strings.TrimSpace(line))
|
||||
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isDigit(fields[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fields[1] != id {
|
||||
continue
|
||||
}
|
||||
|
||||
return []byte(line), nil
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("-1 %s stopped", id)), nil
|
||||
}
|
||||
|
||||
type info struct {
|
||||
ID string
|
||||
driver *driver
|
||||
}
|
||||
|
||||
func (i *info) IsRunning() bool {
|
||||
output, err := i.driver.getInfo(i.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting info for %s container %s: %s (%s)",
|
||||
driverName, i.ID, err, output)
|
||||
return false
|
||||
}
|
||||
|
||||
clrInfo, err := parseClrInfo(i.ID, string(output))
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return clrInfo.Running
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
return &info{
|
||||
ID: id,
|
||||
driver: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
// The VM doesn't expose the worload pid(s), so the only meaningful
|
||||
// pid is that of the VM
|
||||
return []int{d.containerPid}, nil
|
||||
}
|
||||
|
||||
// TtyConsole is a type to represent a pseud-oterminal (see pty(7))
|
||||
type TtyConsole struct {
|
||||
MasterPty *os.File
|
||||
SlavePty *os.File
|
||||
}
|
||||
|
||||
// NewTtyConsole returns a new TtyConsole object.
|
||||
func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
|
||||
// lxc is special in that we cannot create the master outside of the container without
|
||||
// opening the slave because we have nothing to provide to the cmd. We have to open both then do
|
||||
// the crazy setup on command right now instead of passing the console path to lxc and telling it
|
||||
// to open up that console. we save a couple of openfiles in the native driver because we can do
|
||||
// this.
|
||||
ptyMaster, ptySlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty := &TtyConsole{
|
||||
MasterPty: ptyMaster,
|
||||
SlavePty: ptySlave,
|
||||
}
|
||||
|
||||
if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
|
||||
tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processConfig.Console = tty.SlavePty.Name()
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
// Master returns the master end of the pty
|
||||
func (t *TtyConsole) Master() *os.File {
|
||||
return t.MasterPty
|
||||
}
|
||||
|
||||
// Resize modifies the size of the pty terminal being used.
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
|
||||
}
|
||||
|
||||
// AttachPipes associates the specified pipes with the pty master.
|
||||
func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
|
||||
command.Stdout = t.SlavePty
|
||||
command.Stderr = t.SlavePty
|
||||
|
||||
go func() {
|
||||
if wb, ok := pipes.Stdout.(interface {
|
||||
CloseWriters() error
|
||||
}); ok {
|
||||
defer wb.CloseWriters()
|
||||
}
|
||||
|
||||
io.Copy(pipes.Stdout, t.MasterPty)
|
||||
}()
|
||||
|
||||
if pipes.Stdin != nil {
|
||||
command.Stdin = t.SlavePty
|
||||
command.SysProcAttr.Setctty = true
|
||||
|
||||
go func() {
|
||||
io.Copy(t.MasterPty, pipes.Stdin)
|
||||
|
||||
pipes.Stdin.Close()
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes both ends of the pty.
|
||||
func (t *TtyConsole) Close() error {
|
||||
t.SlavePty.Close()
|
||||
return t.MasterPty.Close()
|
||||
}
|
||||
|
||||
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
|
||||
return -1, fmt.Errorf("Unsupported: Exec is not supported by the %q driver", driverName)
|
||||
|
||||
}
|
||||
|
||||
// Clean up after an Exec
|
||||
func (d *driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
||||
data := []byte(strings.Join(c.ProcessConfig.Env, "\n"))
|
||||
|
||||
p := path.Join(d.libPath, "containers", c.ID, "config.env")
|
||||
c.Mounts = append(c.Mounts, execdriver.Mount{
|
||||
Source: p,
|
||||
Destination: "/.dockerenv",
|
||||
Writable: false,
|
||||
Private: true,
|
||||
})
|
||||
|
||||
return ioutil.WriteFile(p, data, 0600)
|
||||
}
|
||||
|
||||
func (d *driver) generateDockerInit(c *execdriver.Command) error {
|
||||
p := fmt.Sprintf("%s/.containerexec", c.Rootfs)
|
||||
var args []string
|
||||
|
||||
if pathExecutable(p) {
|
||||
return nil
|
||||
}
|
||||
|
||||
args = append(args, c.ProcessConfig.Entrypoint)
|
||||
args = append(args, c.ProcessConfig.Arguments...)
|
||||
|
||||
data := []byte(fmt.Sprintf("#!/bin/sh\n%s\n", strings.Join(args, " ")))
|
||||
|
||||
return ioutil.WriteFile(p, data, 0755)
|
||||
}
|
||||
|
||||
func (d *driver) linkExists(name string) bool {
|
||||
cmd := exec.Command("ip", "link", "show", name)
|
||||
err := cmd.Run()
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (d *driver) setupNetwork(c *execdriver.Command) error {
|
||||
ifname := getTapIf(c)
|
||||
|
||||
var bridgeName string
|
||||
var bridgeLinkName string
|
||||
var output []byte
|
||||
var err error
|
||||
var ok bool
|
||||
|
||||
bridge := c.NetworkSettings.Networks["bridge"]
|
||||
if bridge == nil {
|
||||
return fmt.Errorf("no bridge network available")
|
||||
}
|
||||
|
||||
d.ipaddr = bridge.IPAddress
|
||||
d.macaddr = bridge.MacAddress
|
||||
d.gateway = bridge.Gateway
|
||||
|
||||
// Extract the bridge details from the relevant endpoint.
|
||||
for _, info := range c.EndpointInfo {
|
||||
if _, ok = info[netlabel.BridgeEID].(string); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if bridgeName, ok = info[netlabel.BridgeName].(string); !ok {
|
||||
return fmt.Errorf("unable to determine bridge name")
|
||||
}
|
||||
|
||||
if bridgeLinkName, ok = info[netlabel.BridgeLinkName].(string); !ok {
|
||||
return fmt.Errorf("unable to determine bridge link name")
|
||||
}
|
||||
}
|
||||
|
||||
// Strip existing veth if it exists
|
||||
if bridgeLinkName != "" && d.linkExists(bridgeLinkName) {
|
||||
cmd := exec.Command("ip", "link", "del", bridgeLinkName)
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("ip", "tuntap", "add", "dev", ifname, "mode", "tap", "vnet_hdr")
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
|
||||
return err
|
||||
}
|
||||
cmd = exec.Command("ip", "link", "set", "dev", ifname, "master", bridgeName)
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = exec.Command("ip", "link", "set", "dev", ifname, "up")
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
if _, ok := d.activeContainers[id]; !ok {
|
||||
return nil, fmt.Errorf("%s is not a key in active containers", id)
|
||||
}
|
||||
// FIXME:
|
||||
return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
|
||||
}
|
||||
|
||||
func (d *driver) SupportsHooks() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// +build linux
|
||||
|
||||
package clr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCannotParse = errors.New("cannot parse raw input")
|
||||
)
|
||||
|
||||
type clrInfo struct {
|
||||
Running bool
|
||||
Pid int
|
||||
}
|
||||
|
||||
func parseClrInfo(name, raw string) (*clrInfo, error) {
|
||||
if raw == "" {
|
||||
return nil, ErrCannotParse
|
||||
}
|
||||
var (
|
||||
err error
|
||||
info = &clrInfo{}
|
||||
)
|
||||
|
||||
fields := strings.Fields(strings.TrimSpace(raw))
|
||||
|
||||
// The format is expected to be:
|
||||
//
|
||||
// <pid> <name> <state>
|
||||
//
|
||||
if len(fields) != 3 {
|
||||
return nil, ErrCannotParse
|
||||
}
|
||||
|
||||
info.Pid, err = strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
return nil, ErrCannotParse
|
||||
}
|
||||
|
||||
if fields[1] != name {
|
||||
return nil, ErrCannotParse
|
||||
}
|
||||
|
||||
info.Running = fields[2] == "running"
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// +build linux
|
||||
|
||||
package clr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Args provided to the init function for a driver
|
||||
type InitArgs struct {
|
||||
User string
|
||||
Gateway string
|
||||
Ip string
|
||||
WorkDir string
|
||||
Privileged bool
|
||||
Env []string
|
||||
Args []string
|
||||
Mtu int
|
||||
Console string
|
||||
Pipe int
|
||||
Root string
|
||||
CapAdd string
|
||||
CapDrop string
|
||||
}
|
||||
|
||||
func getArgs() *InitArgs {
|
||||
var (
|
||||
// Get cmdline arguments
|
||||
user = flag.String("u", "", "username or uid")
|
||||
gateway = flag.String("g", "", "gateway address")
|
||||
ip = flag.String("i", "", "ip address")
|
||||
workDir = flag.String("w", "", "workdir")
|
||||
privileged = flag.Bool("privileged", false, "privileged mode")
|
||||
mtu = flag.Int("mtu", 1500, "interface mtu")
|
||||
capAdd = flag.String("cap-add", "", "capabilities to add")
|
||||
capDrop = flag.String("cap-drop", "", "capabilities to drop")
|
||||
)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
return &InitArgs{
|
||||
User: *user,
|
||||
Gateway: *gateway,
|
||||
Ip: *ip,
|
||||
WorkDir: *workDir,
|
||||
Privileged: *privileged,
|
||||
Args: flag.Args(),
|
||||
Mtu: *mtu,
|
||||
CapAdd: *capAdd,
|
||||
CapDrop: *capDrop,
|
||||
}
|
||||
}
|
||||
|
||||
// Clear environment pollution introduced by lxc-start
|
||||
func setupEnv(args *InitArgs) error {
|
||||
// Get env
|
||||
var env []string
|
||||
dockerenv, err := os.Open(".dockerenv")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load environment variables: %v", err)
|
||||
}
|
||||
defer dockerenv.Close()
|
||||
if err := json.NewDecoder(dockerenv).Decode(&env); err != nil {
|
||||
return fmt.Errorf("Unable to decode environment variables: %v", err)
|
||||
}
|
||||
// Propagate the plugin-specific container env variable
|
||||
env = append(env, "container="+os.Getenv("container"))
|
||||
|
||||
args.Env = env
|
||||
|
||||
os.Clearenv()
|
||||
for _, kv := range args.Env {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
parts = append(parts, "")
|
||||
}
|
||||
os.Setenv(parts[0], parts[1])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup working directory
|
||||
func setupWorkingDirectory(args *InitArgs) error {
|
||||
if args.WorkDir == "" {
|
||||
return nil
|
||||
}
|
||||
if err := syscall.Chdir(args.WorkDir); err != nil {
|
||||
return fmt.Errorf("Unable to change dir to %v: %v", args.WorkDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
// TODO Windows: Factor out ulimit
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/pkg/ulimit"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
@@ -230,4 +232,8 @@ type Command struct {
|
||||
LayerPaths []string `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
|
||||
LayerFolder string `json:"layer_folder"`
|
||||
Hostname string `json:"hostname"` // Windows sets the hostname in the execdriver
|
||||
NetworkSettings *network.Settings `json:"network_settings"`
|
||||
EndpointInfo []map[string]interface{} `json:"endpoint_info"`
|
||||
HostConfig *runconfig.HostConfig `json:"hostconfig"`
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/execdriver/clr"
|
||||
"github.com/docker/docker/daemon/execdriver/lxc"
|
||||
"github.com/docker/docker/daemon/execdriver/native"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
@@ -15,7 +16,10 @@ import (
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
rootPath := path.Join(root, "execdriver", name)
|
||||
switch name {
|
||||
case "clr":
|
||||
return clr.NewDriver(rootPath, libPath, initPath, sysInfo.AppArmor)
|
||||
case "lxc":
|
||||
// we want to give the lxc driver the full docker root because it needs
|
||||
// to access and write config and template files in /var/lib/docker/containers/*
|
||||
@@ -23,7 +27,7 @@ func NewDriver(name string, options []string, root, libPath, initPath string, sy
|
||||
logrus.Warn("LXC built-in support is deprecated.")
|
||||
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
|
||||
case "native":
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options)
|
||||
return native.NewDriver(rootPath, initPath, options)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown exec driver %s", name)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -148,6 +149,8 @@ type Status struct {
|
||||
Metadata DiskUsage
|
||||
// BaseDeviceSize is base size of container and image
|
||||
BaseDeviceSize uint64
|
||||
// BaseDeviceFS is backing filesystem.
|
||||
BaseDeviceFS string
|
||||
// SectorSize size of the vector.
|
||||
SectorSize uint64
|
||||
// UdevSyncSupported is true if sync is supported.
|
||||
@@ -532,6 +535,45 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *devInfo, ignoreDeleted bo
|
||||
return devicemapper.ActivateDevice(devices.getPoolDevName(), info.Name(), info.DeviceID, info.Size)
|
||||
}
|
||||
|
||||
// Return true only if kernel supports xfs and mkfs.xfs is available
|
||||
func xfsSupported() bool {
|
||||
// Make sure mkfs.xfs is available
|
||||
if _, err := exec.LookPath("mkfs.xfs"); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if kernel supports xfs filesystem or not.
|
||||
exec.Command("modprobe", "xfs").Run()
|
||||
|
||||
f, err := os.Open("/proc/filesystems")
|
||||
if err != nil {
|
||||
logrus.Warnf("Could not check if xfs is supported: %v", err)
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
if strings.HasSuffix(s.Text(), "\txfs") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Err(); err != nil {
|
||||
logrus.Warnf("Could not check if xfs is supported: %v", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func determineDefaultFS() string {
|
||||
if xfsSupported() {
|
||||
return "xfs"
|
||||
}
|
||||
|
||||
logrus.Warn("XFS is not supported in your system. Either the kernel doesnt support it or mkfs.xfs is not in your PATH. Defaulting to ext4 filesystem")
|
||||
return "ext4"
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) createFilesystem(info *devInfo) error {
|
||||
devname := info.DevName()
|
||||
|
||||
@@ -543,6 +585,11 @@ func (devices *DeviceSet) createFilesystem(info *devInfo) error {
|
||||
args = append(args, devname)
|
||||
|
||||
var err error
|
||||
|
||||
if devices.filesystem == "" {
|
||||
devices.filesystem = determineDefaultFS()
|
||||
}
|
||||
|
||||
switch devices.filesystem {
|
||||
case "xfs":
|
||||
err = exec.Command("mkfs.xfs", args...).Run()
|
||||
@@ -844,7 +891,11 @@ func (devices *DeviceSet) getBaseDeviceSize() uint64 {
|
||||
return info.Size
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) verifyBaseDeviceUUID(baseInfo *devInfo) error {
|
||||
func (devices *DeviceSet) getBaseDeviceFS() string {
|
||||
return devices.filesystem
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) verifyBaseDeviceUUIDFS(baseInfo *devInfo) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
@@ -860,9 +911,23 @@ func (devices *DeviceSet) verifyBaseDeviceUUID(baseInfo *devInfo) error {
|
||||
}
|
||||
|
||||
if devices.BaseDeviceUUID != uuid {
|
||||
return fmt.Errorf("Current Base Device UUID:%s does not match with stored UUID:%s", uuid, devices.BaseDeviceUUID)
|
||||
return fmt.Errorf("Current Base Device UUID:%s does not match with stored UUID:%s. Possibly using a different thin pool than last invocation", uuid, devices.BaseDeviceUUID)
|
||||
}
|
||||
|
||||
// If user specified a filesystem using dm.fs option and current
|
||||
// file system of base image is not same, warn user that dm.fs
|
||||
// will be ignored.
|
||||
if devices.filesystem != "" {
|
||||
fs, err := ProbeFsType(baseInfo.DevName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fs != devices.filesystem {
|
||||
logrus.Warnf("Base device already exists and has filesystem %s on it. User specified filesystem %s will be ignored.", fs, devices.filesystem)
|
||||
devices.filesystem = fs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -963,7 +1028,7 @@ func (devices *DeviceSet) checkThinPool() error {
|
||||
|
||||
// Base image is initialized properly. Either save UUID for first time (for
|
||||
// upgrade case or verify UUID.
|
||||
func (devices *DeviceSet) setupVerifyBaseImageUUID(baseInfo *devInfo) error {
|
||||
func (devices *DeviceSet) setupVerifyBaseImageUUIDFS(baseInfo *devInfo) error {
|
||||
// If BaseDeviceUUID is nil (upgrade case), save it and return success.
|
||||
if devices.BaseDeviceUUID == "" {
|
||||
if err := devices.saveBaseDeviceUUID(baseInfo); err != nil {
|
||||
@@ -972,8 +1037,8 @@ func (devices *DeviceSet) setupVerifyBaseImageUUID(baseInfo *devInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := devices.verifyBaseDeviceUUID(baseInfo); err != nil {
|
||||
return fmt.Errorf("Base Device UUID verification failed. Possibly using a different thin pool than last invocation:%v", err)
|
||||
if err := devices.verifyBaseDeviceUUIDFS(baseInfo); err != nil {
|
||||
return fmt.Errorf("Base Device UUID and Filesystem verification failed.%v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -988,7 +1053,7 @@ func (devices *DeviceSet) setupBaseImage() error {
|
||||
|
||||
if oldInfo != nil {
|
||||
if oldInfo.Initialized && !oldInfo.Deleted {
|
||||
if err := devices.setupVerifyBaseImageUUID(oldInfo); err != nil {
|
||||
if err := devices.setupVerifyBaseImageUUIDFS(oldInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2210,6 +2275,7 @@ func (devices *DeviceSet) Status() *Status {
|
||||
status.DeferredDeleteEnabled = devices.deferredDelete
|
||||
status.DeferredDeletedDeviceCount = devices.nrDeletedDevices
|
||||
status.BaseDeviceSize = devices.getBaseDeviceSize()
|
||||
status.BaseDeviceFS = devices.getBaseDeviceFS()
|
||||
|
||||
totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
|
||||
if err == nil {
|
||||
@@ -2270,7 +2336,6 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
|
||||
metaDataLoopbackSize: defaultMetaDataLoopbackSize,
|
||||
baseFsSize: defaultBaseFsSize,
|
||||
overrideUdevSyncCheck: defaultUdevSyncOverride,
|
||||
filesystem: "ext4",
|
||||
doBlkDiscard: true,
|
||||
thinpBlockSize: defaultThinpBlockSize,
|
||||
deviceIDMap: make([]byte, deviceIDMapSz),
|
||||
|
||||
@@ -80,7 +80,7 @@ func (d *Driver) Status() [][2]string {
|
||||
{"Pool Name", s.PoolName},
|
||||
{"Pool Blocksize", fmt.Sprintf("%s", units.HumanSize(float64(s.SectorSize)))},
|
||||
{"Base Device Size", fmt.Sprintf("%s", units.HumanSize(float64(s.BaseDeviceSize)))},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Backing Filesystem", s.BaseDeviceFS},
|
||||
{"Data file", s.DataFile},
|
||||
{"Metadata file", s.MetadataFile},
|
||||
{"Data Space Used", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Used)))},
|
||||
|
||||
+22
-8
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/graphdb"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
@@ -285,6 +286,24 @@ func includeContainerInList(container *Container, ctx *listContext) iterationAct
|
||||
return includeContainer
|
||||
}
|
||||
|
||||
func getImage(s *graph.TagStore, img, imgID string) (string, error) {
|
||||
// both Image and ImageID is actually ids, nothing to guess
|
||||
if strings.HasPrefix(imgID, img) {
|
||||
return img, nil
|
||||
}
|
||||
id, err := s.GetID(img)
|
||||
if err != nil {
|
||||
if err == graph.ErrNameIsNotExist {
|
||||
return imgID, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if id != imgID {
|
||||
return imgID, nil
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// transformContainer generates the container type expected by the docker ps command.
|
||||
func (daemon *Daemon) transformContainer(container *Container, ctx *listContext) (*types.Container, error) {
|
||||
newC := &types.Container{
|
||||
@@ -297,16 +316,11 @@ func (daemon *Daemon) transformContainer(container *Container, ctx *listContext)
|
||||
newC.Names = []string{}
|
||||
}
|
||||
|
||||
img, err := daemon.repositories.LookupImage(container.Config.Image)
|
||||
showImg, err := getImage(daemon.repositories, container.Config.Image, container.ImageID)
|
||||
if err != nil {
|
||||
// If the image can no longer be found by its original reference,
|
||||
// it makes sense to show the ID instead of a stale reference.
|
||||
newC.Image = container.ImageID
|
||||
} else if container.ImageID == img.ID {
|
||||
newC.Image = container.Config.Image
|
||||
} else {
|
||||
newC.Image = container.ImageID
|
||||
return nil, err
|
||||
}
|
||||
newC.Image = showImg
|
||||
|
||||
if len(container.Args) > 0 {
|
||||
args := []string{}
|
||||
|
||||
@@ -142,6 +142,32 @@ func (m *containerMonitor) Start() error {
|
||||
|
||||
m.lastStartTime = time.Now()
|
||||
|
||||
// Make the network settings available to the execution
|
||||
// driver to allow for integration with libnetwork networking.
|
||||
m.container.command.NetworkSettings = m.container.NetworkSettings
|
||||
|
||||
// Allow the execution driver to query memory limits
|
||||
m.container.command.HostConfig = m.container.hostConfig
|
||||
|
||||
// Make the network endpoint details available to the execution
|
||||
// driver, to allow it to determine the bridge name.
|
||||
for name, _ := range m.container.NetworkSettings.Networks {
|
||||
n, _err := m.container.daemon.netController.NetworkByName(name)
|
||||
if _err == nil {
|
||||
var eps []map[string]interface{}
|
||||
|
||||
for _, ep := range n.Endpoints() {
|
||||
info, err := ep.DriverInfo()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
eps = append(eps, info)
|
||||
}
|
||||
|
||||
m.container.command.EndpointInfo = eps
|
||||
}
|
||||
}
|
||||
|
||||
if exitStatus, err = m.container.daemon.run(m.container, pipes, m.callback); err != nil {
|
||||
// if we receive an internal error from the initial start of a container then lets
|
||||
// return it instead of entering the restart loop
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/docker/docker/api/types/versions/v1p20"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
lntypes "github.com/docker/libnetwork/types"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
// ContainerStatsConfig holds information for configuring the runtime
|
||||
@@ -50,10 +48,6 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats
|
||||
var preCPUStats types.CPUStats
|
||||
getStatJSON := func(v interface{}) *types.StatsJSON {
|
||||
update := v.(*execdriver.ResourceStats)
|
||||
// Retrieve the nw statistics from libnetwork and inject them in the Stats
|
||||
if nwStats, err := daemon.getNetworkStats(container); err == nil {
|
||||
update.Stats.Interfaces = nwStats
|
||||
}
|
||||
ss := convertStatsToAPITypes(update.Stats)
|
||||
ss.PreCPUStats = preCPUStats
|
||||
ss.MemoryStats.Limit = uint64(update.MemoryLimit)
|
||||
@@ -133,37 +127,3 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) getNetworkStats(c *Container) ([]*libcontainer.NetworkInterface, error) {
|
||||
var list []*libcontainer.NetworkInterface
|
||||
|
||||
sb, err := daemon.netController.SandboxByID(c.NetworkSettings.SandboxID)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
stats, err := sb.Statistics()
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Convert libnetwork nw stats into libcontainer nw stats
|
||||
for ifName, ifStats := range stats {
|
||||
list = append(list, convertLnNetworkStats(ifName, ifStats))
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
|
||||
n := &libcontainer.NetworkInterface{Name: name}
|
||||
n.RxBytes = stats.RxBytes
|
||||
n.RxPackets = stats.RxPackets
|
||||
n.RxErrors = stats.RxErrors
|
||||
n.RxDropped = stats.RxDropped
|
||||
n.TxBytes = stats.TxBytes
|
||||
n.TxPackets = stats.TxPackets
|
||||
n.TxErrors = stats.TxErrors
|
||||
n.TxDropped = stats.TxDropped
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/pubsub"
|
||||
lntypes "github.com/docker/libnetwork/types"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
)
|
||||
|
||||
@@ -118,6 +120,11 @@ func (s *statsCollector) run() {
|
||||
continue
|
||||
}
|
||||
stats.SystemUsage = systemUsage
|
||||
|
||||
// Retrieve the nw statistics from libnetwork and inject them in the Stats
|
||||
if nwStats, err := s.getNetworkStats(pair.container); err == nil {
|
||||
stats.Interfaces = nwStats
|
||||
}
|
||||
pair.publisher.Publish(stats)
|
||||
}
|
||||
}
|
||||
@@ -170,3 +177,37 @@ func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
|
||||
}
|
||||
return 0, derr.ErrorCodeBadStatFormat
|
||||
}
|
||||
|
||||
func (s *statsCollector) getNetworkStats(c *Container) ([]*libcontainer.NetworkInterface, error) {
|
||||
var list []*libcontainer.NetworkInterface
|
||||
|
||||
sb, err := c.daemon.netController.SandboxByID(c.NetworkSettings.SandboxID)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
stats, err := sb.Statistics()
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Convert libnetwork nw stats into libcontainer nw stats
|
||||
for ifName, ifStats := range stats {
|
||||
list = append(list, convertLnNetworkStats(ifName, ifStats))
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
|
||||
n := &libcontainer.NetworkInterface{Name: name}
|
||||
n.RxBytes = stats.RxBytes
|
||||
n.RxPackets = stats.RxPackets
|
||||
n.RxErrors = stats.RxErrors
|
||||
n.RxDropped = stats.RxDropped
|
||||
n.TxBytes = stats.TxBytes
|
||||
n.TxPackets = stats.TxPackets
|
||||
n.TxErrors = stats.TxErrors
|
||||
n.TxDropped = stats.TxDropped
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -365,9 +365,10 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
||||
}
|
||||
}
|
||||
|
||||
shared := label.IsShared(bind.Mode)
|
||||
if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
|
||||
return err
|
||||
if label.RelabelNeeded(bind.Mode) {
|
||||
if err := label.Relabel(bind.Source, container.MountLabel, label.IsShared(bind.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
binds[bind.Destination] = true
|
||||
mountPoints[bind.Destination] = bind
|
||||
|
||||
+14
-1
@@ -12,7 +12,8 @@ weight=-1
|
||||
# Understand Docker plugins
|
||||
|
||||
You can extend the capabilities of the Docker Engine by loading third-party
|
||||
plugins.
|
||||
plugins. This page explains the types of plugins and provides links to several
|
||||
volume and network plugins for Docker.
|
||||
|
||||
## Types of plugins
|
||||
|
||||
@@ -64,6 +65,18 @@ The following plugins exist:
|
||||
which is written in Go and provides advanced storage functionality for many
|
||||
platforms including EC2, OpenStack, XtremIO, and ScaleIO.
|
||||
|
||||
* The [Contiv Volume Plugin](https://github.com/contiv/volplugin) is an open
|
||||
source volume plugin that provides multi-tenant, persistent, distributed storage
|
||||
with intent based consumption using ceph underneath.
|
||||
|
||||
* The [Contiv Networking](https://github.com/contiv/netplugin) is an open source
|
||||
libnetwork plugin to provide infrastructure and security policies for a
|
||||
multi-tenant micro services deployment, while providing an integration to
|
||||
physical network for non-container workload. Contiv Networking implements the
|
||||
remote driver and IPAM APIs available in Docker 1.9 onwards.
|
||||
|
||||
* The [Weave Network Plugin](https://github.com/weaveworks/docker-plugin) creates a virtual network that connects your Docker containers - across multiple hosts or clouds and enables automatic discovery of applications. Weave networks are resilient, partition tolerant, secure and work in partially connected networks, and other adverse environments - all configured with delightful simplicity.
|
||||
|
||||
## Troubleshooting a plugin
|
||||
|
||||
If you are having problems with Docker after loading a plugin, ask the authors
|
||||
|
||||
@@ -5,7 +5,6 @@ description = "Network driver plugins."
|
||||
keywords = ["Examples, Usage, plugins, docker, documentation, user guide"]
|
||||
[menu.main]
|
||||
parent = "mn_extend"
|
||||
weight=-1
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ from the new repository:
|
||||
|
||||
4. Add the new `gpg` key.
|
||||
|
||||
$ apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
|
||||
$ apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
|
||||
|
||||
5. Open the `/etc/apt/sources.list.d/docker.list` file in your favorite editor.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ packages from the new repository:
|
||||
|
||||
3. Add the new `gpg` key.
|
||||
|
||||
$ sudo apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
|
||||
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
|
||||
|
||||
4. Open the `/etc/apt/sources.list.d/docker.list` file in your favorite editor.
|
||||
|
||||
@@ -67,13 +67,13 @@ packages from the new repository:
|
||||
|
||||
The possible entries are:
|
||||
|
||||
# Ubuntu Precise
|
||||
# Ubuntu Precise 12.04 (LTS)
|
||||
deb https://apt.dockerproject.org/repo ubuntu-precise main
|
||||
# Ubuntu Trusty
|
||||
# Ubuntu Trusty 14.04 (LTS)
|
||||
deb https://apt.dockerproject.org/repo ubuntu-trusty main
|
||||
# Ubuntu Vivid
|
||||
# Ubuntu Vivid 15.04
|
||||
deb https://apt.dockerproject.org/repo ubuntu-vivid main
|
||||
# Ubuntu Wily
|
||||
# Ubuntu Wily 15.10
|
||||
deb https://apt.dockerproject.org/repo ubuntu-wily main
|
||||
|
||||
7. Save and close the `/etc/apt/sources.list.d/docker.list` file.
|
||||
@@ -94,12 +94,31 @@ packages from the new repository:
|
||||
|
||||
### Prerequisites by Ubuntu Version
|
||||
|
||||
The following Ubuntu versions have no additional prerequisites:
|
||||
|
||||
- Ubuntu Wily 15.10
|
||||
- Ubuntu Vivid 15.04
|
||||
- Ubuntu Trusty 14.04 (LTS)
|
||||
|
||||
For Ubuntu Trusty, Vivid, and Wily, it's recommended to install the
|
||||
`linux-image-extra` kernel package. The `linux-image-extra` package
|
||||
allows you use the `aufs` storage driver.
|
||||
|
||||
To install the `linux-image-extra` package for your kernel version:
|
||||
|
||||
1. Open a terminal on your Ubuntu host.
|
||||
|
||||
2. Update your package manager.
|
||||
|
||||
$ sudo apt-get update
|
||||
|
||||
3. Install the recommended package.
|
||||
|
||||
$ sudo apt-get install linux-image-extra-$(uname -r)
|
||||
|
||||
4. Go ahead and install Docker.
|
||||
|
||||
|
||||
#### Ubuntu Precise 12.04 (LTS)
|
||||
|
||||
For Ubuntu Precise, Docker requires the 3.13 kernel version. If your kernel
|
||||
version is older than 3.13, you must upgrade it. Refer to this table to see
|
||||
which packages are required for your environment:
|
||||
|
||||
@@ -124,7 +124,7 @@ list of DNS options to be used in the container.
|
||||
|
||||
### v1.20 API changes
|
||||
|
||||
[Docker Remote API v1.20](docker_remote_api_v1.20/) documentation
|
||||
[Docker Remote API v1.20](docker_remote_api_v1.20.md) documentation
|
||||
|
||||
* `GET /containers/(id)/archive` get an archive of filesystem content from a container.
|
||||
* `PUT /containers/(id)/archive` upload an archive of content to be extracted to
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 7
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST, but for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
||||
`STDIN` and `STDERR`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 6
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST, but for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
||||
`STDIN` and `STDERR`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 5
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST, but for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
||||
`STDIN` and `STDERR`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 4
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST, but for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
||||
`STDIN` and `STDERR`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 3
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST, but for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
||||
`STDIN` and `STDERR`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 2
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST. However, for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `stdout`,
|
||||
`stdin` and `stderr`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight = 1
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST. However, for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `stdout`,
|
||||
`stdin` and `stderr`.
|
||||
|
||||
@@ -15,7 +15,7 @@ weight=-2
|
||||
|
||||
- The Remote API has replaced `rcli`.
|
||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||
[Bind Docker to another host/port or a Unix socket](../../articles/basics.md#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||
[Bind Docker to another host/port or a Unix socket](../../userguide/basics.md#bind-docker-to-another-host-port-or-a-unix-socket).
|
||||
- The API tends to be REST. However, for some complex commands, like `attach`
|
||||
or `pull`, the HTTP connection is hijacked to transport `stdout`,
|
||||
`stdin` and `stderr`.
|
||||
|
||||
@@ -83,7 +83,7 @@ This is useful when you want to set up a container configuration ahead of time
|
||||
so that it is ready to start when you need it. The initial status of the
|
||||
new container is `created`.
|
||||
|
||||
Please see the [run command](run.md) section and the [Docker run reference](run.md) for more details.
|
||||
Please see the [run command](run.md) section and the [Docker run reference](../run.md) for more details.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -261,11 +261,11 @@ options for `zfs` start with `zfs`.
|
||||
* `dm.fs`
|
||||
|
||||
Specifies the filesystem type to use for the base device. The supported
|
||||
options are "ext4" and "xfs". The default is "ext4"
|
||||
options are "ext4" and "xfs". The default is "xfs"
|
||||
|
||||
Example use:
|
||||
|
||||
$ docker daemon --storage-opt dm.fs=xfs
|
||||
$ docker daemon --storage-opt dm.fs=ext4
|
||||
|
||||
* `dm.mkfsarg`
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ parent = "smn_cli"
|
||||
|
||||
Creates a new volume that containers can consume and store data in. If a name is not specified, Docker generates a random name. You create a volume and then configure the container to use it, for example:
|
||||
|
||||
$ docker volume create --name hello
|
||||
hello
|
||||
$ docker run -d -v hello:/world busybox ls /world
|
||||
$ docker volume create --name hello
|
||||
hello
|
||||
|
||||
The mount is created inside the container's `/src` directory. Docker does not support relative paths for mount points inside the container.
|
||||
$ docker run -d -v hello:/world busybox ls /world
|
||||
|
||||
The mount is created inside the container's `/world` directory. Docker does not support relative paths for mount points inside the container.
|
||||
|
||||
Multiple containers can use the same volume in the same time period. This is useful if two containers need access to shared data. For example, if one container writes and the other reads the data.
|
||||
|
||||
@@ -41,7 +42,7 @@ If you specify a volume name already in use on the current driver, Docker assume
|
||||
|
||||
Some volume drivers may take options to customize the volume creation. Use the `-o` or `--opt` flags to pass driver options:
|
||||
|
||||
$ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
|
||||
$ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
|
||||
|
||||
These options are passed directly to the volume driver. Options for
|
||||
different volume drivers may do different things (or nothing at all).
|
||||
|
||||
@@ -37,4 +37,4 @@ Example output:
|
||||
]
|
||||
|
||||
$ docker volume inspect --format '{{ .Mountpoint }}' 85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
|
||||
"/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
|
||||
/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data
|
||||
|
||||
@@ -18,5 +18,5 @@ parent = "smn_cli"
|
||||
|
||||
Removes one or more volumes. You cannot remove a volume that is in use by a container.
|
||||
|
||||
$ docker volume rm hello
|
||||
hello
|
||||
$ docker volume rm hello
|
||||
hello
|
||||
|
||||
@@ -404,16 +404,26 @@ provision the hosts are with Docker Machine.
|
||||
|
||||

|
||||
|
||||
You should open the following ports between each of your hosts.
|
||||
|
||||
| Protocol | Port | Description |
|
||||
|----------|------|-----------------------|
|
||||
| udp | 4789 | Data plane (VXLAN) |
|
||||
| tcp/udp | 7946 | Control plane |
|
||||
|
||||
Your key-value store service may require additional ports.
|
||||
Check your vendor's documentation and open any required ports.
|
||||
|
||||
Once you have several machines provisioned, you can use Docker Swarm to quickly
|
||||
form them into a swarm which includes a discovery service as well.
|
||||
|
||||
To create an overlay network, you configure options on the `daemon` on each
|
||||
Docker Engine for use with `overlay` network. There are two options to set:
|
||||
|
||||
| Option | Description |
|
||||
|----------------------------------|-----------------------------------------------------------|
|
||||
| `--cluster-store=PROVIDER://URL` | Describes the location of the KV service. |
|
||||
| `--cluster-advertise=HOST_IP` | Advertises containers created by the HOST on the network. |
|
||||
| Option | Description |
|
||||
|-----------------------------------------------|-------------------------------------------------------------|
|
||||
| `--cluster-store=PROVIDER://URL` | Describes the location of the KV service. |
|
||||
| `--cluster-advertise=HOST_IP|HOST_IFACE:PORT` | The IP address or interface of the HOST used for clustering |
|
||||
|
||||
Create an `overlay` network on one of the machines in the Swarm.
|
||||
|
||||
@@ -426,7 +436,7 @@ provides complete isolation for the containers.
|
||||
|
||||
Then, on each host, launch containers making sure to specify the network name.
|
||||
|
||||
$ docker run -itd --net=mmy-multi-host-network busybox
|
||||
$ docker run -itd --net=my-multi-host-network busybox
|
||||
|
||||
Once connected, each container has access to all the containers in the network
|
||||
regardless of which Docker host the container was launched on.
|
||||
@@ -477,4 +487,4 @@ and removed in a future release.
|
||||
- [Managing Data in Containers](../dockervolumes.md)
|
||||
- [Docker Machine overview](https://docs.docker.com/machine)
|
||||
- [Docker Swarm overview](https://docs.docker.com/swarm)
|
||||
- [Investigate the LibNetwork project](https://github.com/docker/libnetwork/blob/master)
|
||||
- [Investigate the LibNetwork project](https://github.com/docker/libnetwork)
|
||||
|
||||
@@ -7,13 +7,13 @@ keywords = ["Examples, Usage, network, docker, documentation, user guide, multih
|
||||
parent = "smn_networking"
|
||||
weight=-3
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
<![end-metadata]-->
|
||||
|
||||
# Get started with multi-host networking
|
||||
|
||||
This article uses an example to explain the basics of creating a multi-host
|
||||
network. Docker Engine supports multi-host-networking out-of-the-box through the
|
||||
`overlay` network driver. Unlike `bridge` networks overlay networks require
|
||||
network. Docker Engine supports multi-host networking out-of-the-box through the
|
||||
`overlay` network driver. Unlike `bridge` networks, overlay networks require
|
||||
some pre-existing conditions before you can create one. These conditions are:
|
||||
|
||||
* A host with a 3.16 kernel version or higher.
|
||||
@@ -22,8 +22,8 @@ some pre-existing conditions before you can create one. These conditions are:
|
||||
* A properly configured Engine `daemon` on each host in the cluster.
|
||||
|
||||
Though Docker Machine and Docker Swarm are not mandatory to experience Docker
|
||||
multi-host-networking, this example uses them to illustrate how they are
|
||||
integrated. You'll use Machine to create both the the key-value store
|
||||
multi-host networking, this example uses them to illustrate how they are
|
||||
integrated. You'll use Machine to create both the key-value store
|
||||
server and the host cluster. This example creates a Swarm cluster.
|
||||
|
||||
## Prerequisites
|
||||
@@ -39,10 +39,10 @@ Machine to the latest versions.
|
||||
|
||||
## Step 1: Set up a key-value store
|
||||
|
||||
An overlay network requires a key-value store. The key-value stores information
|
||||
about the network state which includes discovery, networks, endpoints,
|
||||
ip-addresses, and more. Docker supports Consul, Etcd, and ZooKeeper (Distributed
|
||||
store) key-value stores. This example uses Consul.
|
||||
An overlay network requires a key-value store. The key-value store holds
|
||||
information about the network state which includes discovery, networks,
|
||||
endpoints, IP addresses, and more. Docker supports Consul, Etcd, and ZooKeeper
|
||||
key-value stores. This example uses Consul.
|
||||
|
||||
1. Log into a system prepared with the prerequisite Docker Engine, Docker Machine, and VirtualBox software.
|
||||
|
||||
@@ -62,9 +62,10 @@ store) key-value stores. This example uses Consul.
|
||||
-h "consul" \
|
||||
progrium/consul -server -bootstrap
|
||||
|
||||
You passed the `docker run` command the connection configuration using a bash
|
||||
expansion `$(docker-machine config mh-keystore)`. The client started a
|
||||
`progrium/consul` image running in the `mh-keystore` machine. The server is called `consul`and is listening port `8500`.
|
||||
A bash expansion `$(docker-machine config mh-keystore)` is used to pass the
|
||||
connection configuration to the `docker run` command. The client starts a
|
||||
`progrium/consul` image running in the `mh-keystore` machine. The server is
|
||||
called `consul` and is listening on port `8500`.
|
||||
|
||||
4. Set your local environment to the `mh-keystore` machine.
|
||||
|
||||
@@ -82,7 +83,7 @@ Keep your terminal open and move onto the next step.
|
||||
## Step 2: Create a Swarm cluster
|
||||
|
||||
In this step, you use `docker-machine` to provision the hosts for your network.
|
||||
At this point, you won't actually created the network. You'll create several
|
||||
At this point, you won't actually create the network. You'll create several
|
||||
machines in VirtualBox. One of the machines will act as the Swarm master;
|
||||
you'll create that first. As you create each host, you'll pass the Engine on
|
||||
that machine options that are needed by the `overlay` network driver.
|
||||
@@ -91,7 +92,7 @@ that machine options that are needed by the `overlay` network driver.
|
||||
|
||||
$ docker-machine create \
|
||||
-d virtualbox \
|
||||
--swarm --swarm-image="swarm" --swarm-master \
|
||||
--swarm --swarm-master \
|
||||
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
||||
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
|
||||
--engine-opt="cluster-advertise=eth1:2376" \
|
||||
@@ -102,7 +103,7 @@ that machine options that are needed by the `overlay` network driver.
|
||||
2. Create another host and add it to the Swarm cluster.
|
||||
|
||||
$ docker-machine create -d virtualbox \
|
||||
--swarm --swarm-image="swarm:1.0.0-rc2" \
|
||||
--swarm \
|
||||
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
||||
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
|
||||
--engine-opt="cluster-advertise=eth1:2376" \
|
||||
@@ -112,14 +113,13 @@ that machine options that are needed by the `overlay` network driver.
|
||||
|
||||
$ docker-machine ls
|
||||
NAME ACTIVE DRIVER STATE URL SWARM
|
||||
default virtualbox Running tcp://192.168.99.100:2376
|
||||
mh-keystore virtualbox Running tcp://192.168.99.103:2376
|
||||
mhs-demo0 virtualbox Running tcp://192.168.99.104:2376 mhs-demo0 (master)
|
||||
mhs-demo1 virtualbox Running tcp://192.168.99.105:2376 mhs-demo0
|
||||
default - virtualbox Running tcp://192.168.99.100:2376
|
||||
mh-keystore * virtualbox Running tcp://192.168.99.103:2376
|
||||
mhs-demo0 - virtualbox Running tcp://192.168.99.104:2376 mhs-demo0 (master)
|
||||
mhs-demo1 - virtualbox Running tcp://192.168.99.105:2376 mhs-demo0
|
||||
|
||||
At this point you have a set of hosts running on your network. You are ready to create a multi-host network for containers using these hosts.
|
||||
|
||||
|
||||
Leave your terminal open and go onto the next step.
|
||||
|
||||
## Step 3: Create the overlay Network
|
||||
@@ -155,7 +155,7 @@ To create an overlay network
|
||||
Total Memory: 2.043 GiB
|
||||
Name: 30438ece0915
|
||||
|
||||
From this information, you can see that you are running three containers and 2 images on the Master.
|
||||
From this information, you can see that you are running three containers and two images on the Master.
|
||||
|
||||
3. Create your `overlay` network.
|
||||
|
||||
@@ -167,54 +167,51 @@ To create an overlay network
|
||||
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER
|
||||
412c2496d0eb mhs-demo1/host host
|
||||
dd51763e6dd2 mhs-demo0/bridge bridge
|
||||
6b07d0be843f my-net overlay
|
||||
b4234109bd9b mhs-demo0/none null
|
||||
1aeead6dd890 mhs-demo0/host host
|
||||
d0bb78cbe7bd mhs-demo1/bridge bridge
|
||||
1c0eb8f69ebb mhs-demo1/none null
|
||||
412c2496d0eb mhs-demo1/host host
|
||||
dd51763e6dd2 mhs-demo0/bridge bridge
|
||||
6b07d0be843f my-net overlay
|
||||
b4234109bd9b mhs-demo0/none null
|
||||
1aeead6dd890 mhs-demo0/host host
|
||||
d0bb78cbe7bd mhs-demo1/bridge bridge
|
||||
1c0eb8f69ebb mhs-demo1/none null
|
||||
|
||||
Because you are in the Swarm master environment, you see all the networks on all Swarm agents. Notice that each `NETWORK ID` is unique. The default networks on each engine and the single overlay network.
|
||||
As you are in the Swarm master environment, you see all the networks on all
|
||||
the Swarm agents: the default networks on each engine and the single overlay
|
||||
network. Notice that each `NETWORK ID` is unique.
|
||||
|
||||
5. Switch to each Swarm agent in turn and list the network.
|
||||
5. Switch to each Swarm agent in turn and list the networks.
|
||||
|
||||
$ eval $(docker-machine env mhs-demo0)
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER
|
||||
6b07d0be843f my-net overlay
|
||||
dd51763e6dd2 bridge bridge
|
||||
b4234109bd9b none null
|
||||
1aeead6dd890 host host
|
||||
6b07d0be843f my-net overlay
|
||||
dd51763e6dd2 bridge bridge
|
||||
b4234109bd9b none null
|
||||
1aeead6dd890 host host
|
||||
$ eval $(docker-machine env mhs-demo1)
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER
|
||||
d0bb78cbe7bd bridge bridge
|
||||
1c0eb8f69ebb none null
|
||||
412c2496d0eb host host
|
||||
6b07d0be843f my-net overlay
|
||||
d0bb78cbe7bd bridge bridge
|
||||
1c0eb8f69ebb none null
|
||||
412c2496d0eb host host
|
||||
6b07d0be843f my-net overlay
|
||||
|
||||
Both agents reports it has the `my-net `network with the `6b07d0be843f` id. You have a multi-host container network running!
|
||||
Both agents report they have the `my-net` network with the `6b07d0be843f` ID.
|
||||
You now have a multi-host container network running!
|
||||
|
||||
## Step 4: Run an application on your Network
|
||||
|
||||
Once your network is created, you can start a container on any of the hosts and it automatically is part of the network.
|
||||
|
||||
1. Point your environment to your `mhs-demo0` instance.
|
||||
1. Point your environment to the Swarm master.
|
||||
|
||||
$ eval $(docker-machine env mhs-demo0)
|
||||
$ eval $(docker-machine env --swarm mhs-demo0)
|
||||
|
||||
2. Start an Nginx server on `mhs-demo0`.
|
||||
2. Start an Nginx web server on the `mhs-demo0` instance.
|
||||
|
||||
$ docker run -itd --name=web --net=my-net --env="constraint:node==mhs-demo0" nginx
|
||||
|
||||
This command starts a web server on the Swarm master.
|
||||
|
||||
3. Point your Machine environment to `mhs-demo1`
|
||||
|
||||
$ eval $(docker-machine env mhs-demo1)
|
||||
|
||||
4. Run a Busybox instance and get the contents of the Ngnix server's home page.
|
||||
4. Run a BusyBox instance on the `mhs-demo1` instance and get the contents of the Nginx server's home page.
|
||||
|
||||
$ docker run -it --rm --net=my-net --env="constraint:node==mhs-demo1" busybox wget -O- http://web
|
||||
Unable to find image 'busybox:latest' locally
|
||||
@@ -284,7 +281,7 @@ to have external connectivity outside of their cluster.
|
||||
412c2496d0eb host host
|
||||
97102a22e8d2 docker_gwbridge bridge
|
||||
|
||||
2. Check the Ngnix container's network interfaces.
|
||||
2. Check the Nginx container's network interfaces.
|
||||
|
||||
$ docker exec web ip addr
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
|
||||
@@ -314,9 +311,11 @@ to have external connectivity outside of their cluster.
|
||||
|
||||
You can try starting a second network on your existing Swarm cluster using Docker Compose.
|
||||
|
||||
1. Log into the Swarm master.
|
||||
1. If you haven't already, install Docker Compose.
|
||||
|
||||
2. Install Docker Compose.
|
||||
2. Change your environment to the Swarm master.
|
||||
|
||||
$ eval $(docker-machine env --swarm mhs-demo0)
|
||||
|
||||
3. Create a `docker-compose.yml` file.
|
||||
|
||||
@@ -326,7 +325,7 @@ You can try starting a second network on your existing Swarm cluster using Docke
|
||||
image: bfirsh/compose-mongodb-demo
|
||||
environment:
|
||||
- "MONGO_HOST=counter_mongo_1"
|
||||
- "constraint:node==swl-demo0"
|
||||
- "constraint:node==mhs-demo0"
|
||||
ports:
|
||||
- "80:5000"
|
||||
mongo:
|
||||
@@ -336,7 +335,15 @@ You can try starting a second network on your existing Swarm cluster using Docke
|
||||
|
||||
6. Start the application with Compose.
|
||||
|
||||
$ docker-compose up --x-networking up -d
|
||||
$ docker-compose --x-networking --project-name=counter up -d
|
||||
|
||||
7. Get the Swarm master's IP address.
|
||||
|
||||
$ docker-machine ip mhs-demo0
|
||||
|
||||
8. Put the IP address into your web browser.
|
||||
|
||||
Upon success, the browser should display the web application.
|
||||
|
||||
## Related information
|
||||
|
||||
|
||||
@@ -193,5 +193,5 @@ reason, you may want to place heavy write workloads on data volumes.
|
||||
|
||||
* [Understand images, containers, and storage drivers](imagesandcontainers.md)
|
||||
* [Select a storage driver](selectadriver.md)
|
||||
* [BTRFS storage driver in practice](btrfs-driver.md)
|
||||
* [Btrfs storage driver in practice](btrfs-driver.md)
|
||||
* [Device Mapper storage driver in practice](device-mapper-driver.md)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!--[metadata]>
|
||||
+++
|
||||
title = "BTRFS storage in practice"
|
||||
description = "Learn how to optimize your use of BTRFS driver."
|
||||
keywords = ["container, storage, driver, BTRFS "]
|
||||
title = "Btrfs storage in practice"
|
||||
description = "Learn how to optimize your use of Btrfs driver."
|
||||
keywords = ["container, storage, driver, Btrfs "]
|
||||
[menu.main]
|
||||
parent = "mn_storage_docker"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Docker and BTRFS in practice
|
||||
# Docker and Btrfs in practice
|
||||
|
||||
Btrfs is a next generation copy-on-write filesystem that supports many advanced
|
||||
storage technologies that make it a good fit for Docker. Btrfs is included in
|
||||
@@ -250,7 +250,7 @@ Now that you have a Btrfs filesystem mounted at `/var/lib/docker`, the daemon sh
|
||||
|
||||
Your Docker host is now configured to use the `btrfs` storage driver.
|
||||
|
||||
## BTRFS and Docker performance
|
||||
## Btrfs and Docker performance
|
||||
|
||||
There are several factors that influence Docker's performance under the `btrfs` storage driver.
|
||||
|
||||
|
||||
@@ -307,4 +307,4 @@ One final point, data volumes provide the best and most predictable performance.
|
||||
* [Understand images, containers, and storage drivers](imagesandcontainers.md)
|
||||
* [Select a storage driver](selectadriver.md)
|
||||
* [AUFS storage driver in practice](aufs-driver.md)
|
||||
* [BTRFS storage driver in practice](btrfs-driver.md)
|
||||
* [Btrfs storage driver in practice](btrfs-driver.md)
|
||||
|
||||
@@ -251,5 +251,5 @@ For detailed information about data volumes [Managing data in containers](https:
|
||||
|
||||
* [Select a storage driver](selectadriver.md)
|
||||
* [AUFS storage driver in practice](aufs-driver.md)
|
||||
* [BTRFS storage driver in practice](btrfs-driver.md)
|
||||
* [Btrfs storage driver in practice](btrfs-driver.md)
|
||||
* [Device Mapper storage driver in practice](device-mapper-driver.md)
|
||||
|
||||
@@ -18,10 +18,10 @@ Docker relies on driver technology to manage the storage and interactions associ
|
||||
* [Understand images, containers, and storage drivers](imagesandcontainers.md)
|
||||
* [Select a storage driver](selectadriver.md)
|
||||
* [AUFS storage driver in practice](aufs-driver.md)
|
||||
* [BTRFS storage driver in practice](btrfs-driver.md)
|
||||
* [Btrfs storage driver in practice](btrfs-driver.md)
|
||||
* [Device Mapper storage driver in practice](device-mapper-driver.md)
|
||||
* [OverlayFS in practice](overlayfs-driver.md)
|
||||
* [FS storage in practice](zfs-driver.md)
|
||||
* [ZFS storage in practice](zfs-driver.md)
|
||||
|
||||
If you are new to Docker containers make sure you read ["Understand images, containers, and storage drivers"](imagesandcontainers.md) first. It explains key concepts and technologies that can help you when working with storage drivers.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Once you decide which driver is best, you set this driver on the Docker daemon a
|
||||
|--------------|---------------------|
|
||||
|OverlayFS |`overlay` |
|
||||
|AUFS |`aufs` |
|
||||
|BTRFS |`btrfs` |
|
||||
|Btrfs |`btrfs` |
|
||||
|Device Maper |`devicemapper` |
|
||||
|VFS* |`vfs` |
|
||||
|ZFS |`zfs` |
|
||||
@@ -115,5 +115,5 @@ Whichever driver you choose, make sure it has strong community support and momen
|
||||
|
||||
* [Understand images, containers, and storage drivers](imagesandcontainers.md)
|
||||
* [AUFS storage driver in practice](aufs-driver.md)
|
||||
* [BTRFS storage driver in practice](btrfs-driver.md)
|
||||
* [Btrfs storage driver in practice](btrfs-driver.md)
|
||||
* [Device Mapper storage driver in practice](device-mapper-driver.md)
|
||||
|
||||
+1
-4
@@ -182,10 +182,7 @@ func (graph *Graph) restore() error {
|
||||
if graph.driver.Exists(id) {
|
||||
img, err := graph.loadImage(id)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return fmt.Errorf("could not restore image %s: %v", id, err)
|
||||
}
|
||||
logrus.Warnf("could not restore image %s due to corrupted files", id)
|
||||
logrus.Warnf("ignoring image %s, it could not be restored: %v", id, err)
|
||||
continue
|
||||
}
|
||||
graph.imageMutex.Lock(img.Parent)
|
||||
|
||||
@@ -3,6 +3,8 @@ package graph
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -199,6 +201,11 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
|
||||
p.layersPushed[dgst] = true
|
||||
}
|
||||
|
||||
// Fix parent chain if necessary
|
||||
if err = fixHistory(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())
|
||||
signed, err := manifest.Sign(m, p.trustKey)
|
||||
if err != nil {
|
||||
@@ -220,6 +227,90 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
|
||||
return manSvc.Put(signed)
|
||||
}
|
||||
|
||||
// fixHistory makes sure that the manifest has parent IDs that are consistent
|
||||
// with its image IDs. Because local image IDs are generated from the
|
||||
// configuration and filesystem contents, but IDs in the manifest are preserved
|
||||
// from the original pull, it's possible to have inconsistencies where parent
|
||||
// IDs don't match up with the other IDs in the manifest. This happens in the
|
||||
// case where an engine pulls images where are identical except the IDs from the
|
||||
// manifest - the local ID will be the same, and one of the v1Compatibility
|
||||
// files gets discarded.
|
||||
func fixHistory(m *manifest.Manifest) error {
|
||||
var lastID string
|
||||
|
||||
for i := len(m.History) - 1; i >= 0; i-- {
|
||||
var historyEntry map[string]*json.RawMessage
|
||||
if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &historyEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idJSON, present := historyEntry["id"]
|
||||
if !present || idJSON == nil {
|
||||
return errors.New("missing id key in v1compatibility file")
|
||||
}
|
||||
var id string
|
||||
if err := json.Unmarshal(*idJSON, &id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentJSON, present := historyEntry["parent"]
|
||||
|
||||
if i == len(m.History)-1 {
|
||||
// The base layer must not reference a parent layer,
|
||||
// otherwise the manifest is incomplete. There is an
|
||||
// exception for Windows to handle base layers.
|
||||
if present && parentJSON != nil {
|
||||
var parent string
|
||||
if err := json.Unmarshal(*parentJSON, &parent); err != nil {
|
||||
return err
|
||||
}
|
||||
if parent != "" {
|
||||
logrus.Debugf("parent id mismatch detected; fixing. parent reference: %s", parent)
|
||||
delete(historyEntry, "parent")
|
||||
fixedHistory, err := json.Marshal(historyEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.History[i].V1Compatibility = string(fixedHistory)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For all other layers, the parent ID should equal the
|
||||
// ID of the next item in the history list. If it
|
||||
// doesn't, fix it up (but preserve all other fields,
|
||||
// possibly including fields that aren't known to this
|
||||
// engine version).
|
||||
if !present || parentJSON == nil {
|
||||
return errors.New("missing parent key in v1compatibility file")
|
||||
}
|
||||
var parent string
|
||||
if err := json.Unmarshal(*parentJSON, &parent); err != nil {
|
||||
return err
|
||||
}
|
||||
if parent != lastID {
|
||||
logrus.Debugf("parent id mismatch detected; fixing. parent reference: %s actual id: %s", parent, id)
|
||||
historyEntry["parent"] = rawJSON(lastID)
|
||||
fixedHistory, err := json.Marshal(historyEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.History[i].V1Compatibility = string(fixedHistory)
|
||||
}
|
||||
}
|
||||
lastID = id
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rawJSON(value interface{}) *json.RawMessage {
|
||||
jsonval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return (*json.RawMessage)(&jsonval)
|
||||
}
|
||||
|
||||
func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) {
|
||||
out := p.config.OutStream
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ import (
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
// ErrNameIsNotExist returned when there is no image with requested name.
|
||||
var ErrNameIsNotExist = errors.New("image with specified name does not exist")
|
||||
|
||||
// TagStore manages repositories. It encompasses the Graph used for versioned
|
||||
// storage, as well as various services involved in pushing and pulling
|
||||
// repositories.
|
||||
@@ -164,6 +167,26 @@ func (store *TagStore) LookupImage(name string) (*image.Image, error) {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// GetID returns ID for image name.
|
||||
func (store *TagStore) GetID(name string) (string, error) {
|
||||
repoName, ref := parsers.ParseRepositoryTag(name)
|
||||
if ref == "" {
|
||||
ref = tags.DefaultTag
|
||||
}
|
||||
store.Lock()
|
||||
defer store.Unlock()
|
||||
repoName = registry.NormalizeLocalName(repoName)
|
||||
repo, ok := store.Repositories[repoName]
|
||||
if !ok {
|
||||
return "", ErrNameIsNotExist
|
||||
}
|
||||
id, ok := repo[ref]
|
||||
if !ok {
|
||||
return "", ErrNameIsNotExist
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// ByID returns a reverse-lookup table of all the names which refer to each
|
||||
// image - e.g. {"43b5f19b10584": {"base:latest", "base:v1"}}
|
||||
func (store *TagStore) ByID() map[string][]string {
|
||||
|
||||
+62
-2
@@ -80,6 +80,16 @@ check_forked() {
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_import_repository_key() {
|
||||
local key=$1; shift
|
||||
local tmpdir=$(mktemp -d)
|
||||
chmod 600 "$tmpdir"
|
||||
gpg --homedir "$tmpdir" --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"
|
||||
gpg --homedir "$tmpdir" --export --armor "$key" > "$tmpdir"/repo.key
|
||||
rpm --import "$tmpdir"/repo.key
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
|
||||
do_install() {
|
||||
case "$(uname -m)" in
|
||||
*64)
|
||||
@@ -231,10 +241,60 @@ do_install() {
|
||||
exit 0
|
||||
;;
|
||||
|
||||
'opensuse project'|opensuse|'suse linux'|sle[sd])
|
||||
'opensuse project'|opensuse)
|
||||
echo 'Going to perform the following operations:'
|
||||
if [ "$repo" != 'main' ]; then
|
||||
echo ' * add repository obs://Virtualization:containers'
|
||||
fi
|
||||
echo ' * install Docker'
|
||||
$sh_c 'echo "Press CTRL-C to abort"; sleep 3'
|
||||
|
||||
if [ "$repo" != 'main' ]; then
|
||||
# install experimental packages from OBS://Virtualization:containers
|
||||
(
|
||||
set -x
|
||||
zypper -n ar -f obs://Virtualization:containers Virtualization:containers
|
||||
rpm_import_repository_key 55A0B34D49501BB7CA474F5AA193FBB572174FC2
|
||||
)
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; zypper -n install docker'
|
||||
zypper -n install docker
|
||||
)
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
;;
|
||||
'suse linux'|sle[sd])
|
||||
echo 'Going to perform the following operations:'
|
||||
if [ "$repo" != 'main' ]; then
|
||||
echo ' * add repository obs://Virtualization:containers'
|
||||
echo ' * install experimental Docker using packages NOT supported by SUSE'
|
||||
else
|
||||
echo ' * add the "Containers" module'
|
||||
echo ' * install Docker using packages supported by SUSE'
|
||||
fi
|
||||
$sh_c 'echo "Press CTRL-C to abort"; sleep 3'
|
||||
|
||||
if [ "$repo" != 'main' ]; then
|
||||
# install experimental packages from OBS://Virtualization:containers
|
||||
echo >&2 'Warning: installing experimental packages from OBS, these packages are NOT supported by SUSE'
|
||||
(
|
||||
set -x
|
||||
zypper -n ar -f obs://Virtualization:containers/SLE_12 Virtualization:containers
|
||||
rpm_import_repository_key 55A0B34D49501BB7CA474F5AA193FBB572174FC2
|
||||
)
|
||||
else
|
||||
# Add the containers module
|
||||
# Note well-1: the SLE machine must already be registered against SUSE Customer Center
|
||||
# Note well-2: the `-r ""` is required to workaround a known issue of SUSEConnect
|
||||
(
|
||||
set -x
|
||||
SUSEConnect -p sle-module-containers/12/x86_64 -r ""
|
||||
)
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
zypper -n install docker
|
||||
)
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
|
||||
+4
-2
@@ -20,7 +20,7 @@ clone git github.com/tchap/go-patricia v2.1.0
|
||||
clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://github.com/golang/net.git
|
||||
|
||||
#get libnetwork packages
|
||||
clone git github.com/docker/libnetwork e7719596c01a83f9ef24d33e9d609a64acacd7b8
|
||||
clone git github.com/docker/libnetwork b4ddf18317b19d6e4bcc821145589749206a7d00
|
||||
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
||||
clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
|
||||
clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4
|
||||
@@ -46,7 +46,9 @@ clone git github.com/endophage/gotuf 2df1c8e0a7b7e10ae2113bf37aaa1bf1c1de8cc5
|
||||
clone git github.com/jfrazelle/go 6e461eb70cb4187b41a84e9a567d7137bdbe0f16
|
||||
clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
|
||||
|
||||
clone git github.com/opencontainers/runc 6c198ae2d065c37f44316e0de3df7f3b88950923 # libcontainer
|
||||
# this runc commit from branch relabel_fix_docker_1.9.1, pls remove it when you
|
||||
# update next time
|
||||
clone git github.com/opencontainers/runc 1349b37bd56f4f5ce2690b5b2c0f53f88a261c67 # libcontainer
|
||||
# libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json)
|
||||
clone git github.com/coreos/go-systemd v3
|
||||
clone git github.com/godbus/dbus v2
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/builder/dockerfile/command"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
@@ -6223,3 +6224,127 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
|
||||
_, err := buildImage("test", dockerFile, false)
|
||||
c.Assert(err, check.NotNil, check.Commentf("image build should have failed"))
|
||||
}
|
||||
|
||||
// #17290
|
||||
func (s *DockerSuite) TestBuildCacheBrokenSymlink(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
name := "testbuildbrokensymlink"
|
||||
ctx, err := fakeContext(`
|
||||
FROM busybox
|
||||
COPY . ./`,
|
||||
map[string]string{
|
||||
"foo": "bar",
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer ctx.Close()
|
||||
|
||||
err = os.Symlink(filepath.Join(ctx.Dir, "nosuchfile"), filepath.Join(ctx.Dir, "asymlink"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// warm up cache
|
||||
_, err = buildImageFromContext(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// add new file to context, should invalidate cache
|
||||
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "newfile"), []byte("foo"), 0644)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
_, out, err := buildImageFromContextWithOut(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
c.Assert(out, checker.Not(checker.Contains), "Using cache")
|
||||
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestBuildFollowSymlinkToFile(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
name := "testbuildbrokensymlink"
|
||||
ctx, err := fakeContext(`
|
||||
FROM busybox
|
||||
COPY asymlink target`,
|
||||
map[string]string{
|
||||
"foo": "bar",
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer ctx.Close()
|
||||
|
||||
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
id, err := buildImageFromContext(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "target")
|
||||
c.Assert(out, checker.Matches, "bar")
|
||||
|
||||
// change target file should invalidate cache
|
||||
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0644)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
id, out, err = buildImageFromContextWithOut(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(out, checker.Not(checker.Contains), "Using cache")
|
||||
|
||||
out, _ = dockerCmd(c, "run", "--rm", id, "cat", "target")
|
||||
c.Assert(out, checker.Matches, "baz")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestBuildFollowSymlinkToDir(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
name := "testbuildbrokensymlink"
|
||||
ctx, err := fakeContext(`
|
||||
FROM busybox
|
||||
COPY asymlink /`,
|
||||
map[string]string{
|
||||
"foo/abc": "bar",
|
||||
"foo/def": "baz",
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer ctx.Close()
|
||||
|
||||
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
id, err := buildImageFromContext(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "abc", "def")
|
||||
c.Assert(out, checker.Matches, "barbaz")
|
||||
|
||||
// change target file should invalidate cache
|
||||
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "foo/def"), []byte("bax"), 0644)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
id, out, err = buildImageFromContextWithOut(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(out, checker.Not(checker.Contains), "Using cache")
|
||||
|
||||
out, _ = dockerCmd(c, "run", "--rm", id, "cat", "abc", "def")
|
||||
c.Assert(out, checker.Matches, "barbax")
|
||||
|
||||
}
|
||||
|
||||
// TestBuildSymlinkBasename tests that target file gets basename from symlink,
|
||||
// not from the target file.
|
||||
func (s *DockerSuite) TestBuildSymlinkBasename(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
name := "testbuildbrokensymlink"
|
||||
ctx, err := fakeContext(`
|
||||
FROM busybox
|
||||
COPY asymlink /`,
|
||||
map[string]string{
|
||||
"foo": "bar",
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer ctx.Close()
|
||||
|
||||
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
id, err := buildImageFromContext(name, ctx, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "asymlink")
|
||||
c.Assert(out, checker.Matches, "bar")
|
||||
|
||||
}
|
||||
|
||||
@@ -377,6 +377,29 @@ func (s *DockerSuite) TestDaemonIPv6FixedCIDR(c *check.C) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDaemonIPv6FixedCIDRAndMac checks that when the daemon is started with ipv6 fixed CIDR
|
||||
// the running containers are given a an IPv6 address derived from the MAC address and the ipv6 fixed CIDR
|
||||
func (s *DockerSuite) TestDaemonIPv6FixedCIDRAndMac(c *check.C) {
|
||||
err := setupV6()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
d := NewDaemon(c)
|
||||
|
||||
err = d.StartWithBusybox("--ipv6", "--fixed-cidr-v6='2001:db8:1::/64'")
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer d.Stop()
|
||||
|
||||
out, err := d.Cmd("run", "-itd", "--name=ipv6test", "--mac-address", "AA:BB:CC:DD:EE:FF", "busybox")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.Trim(out, " \r\n'"), checker.Equals, "2001:db8:1::aabb:ccdd:eeff")
|
||||
|
||||
err = teardownV6()
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) {
|
||||
c.Assert(s.d.Start("--log-level=bogus"), check.NotNil, check.Commentf("Daemon shouldn't start with wrong log level"))
|
||||
}
|
||||
@@ -794,7 +817,33 @@ func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidrFixedCIDREqualBridgeNetwork(c *check.C) {
|
||||
func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr2(c *check.C) {
|
||||
d := s.d
|
||||
|
||||
bridgeName := "external-bridge"
|
||||
bridgeIP := "10.2.2.1/16"
|
||||
|
||||
out, err := createInterface(c, "bridge", bridgeName, bridgeIP)
|
||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||
defer deleteInterface(c, bridgeName)
|
||||
|
||||
err = d.StartWithBusybox("--bip", bridgeIP, "--fixed-cidr", "10.2.2.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer s.d.Restart()
|
||||
|
||||
out, err = d.Cmd("run", "-d", "--name", "bb", "busybox", "top")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
defer d.Cmd("stop", "bb")
|
||||
|
||||
out, err = d.Cmd("exec", "bb", "/bin/sh", "-c", "ifconfig eth0 | awk '/inet addr/{print substr($2,6)}'")
|
||||
c.Assert(out, checker.Equals, "10.2.2.0\n")
|
||||
|
||||
out, err = d.Cmd("run", "--rm", "busybox", "/bin/sh", "-c", "ifconfig eth0 | awk '/inet addr/{print substr($2,6)}'")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(out, checker.Equals, "10.2.2.2\n")
|
||||
}
|
||||
|
||||
func (s *DockerDaemonSuite) TestDaemonBridgeFixedCIDREqualBridgeNetwork(c *check.C) {
|
||||
d := s.d
|
||||
|
||||
bridgeName := "external-bridge"
|
||||
@@ -1242,10 +1291,10 @@ func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) {
|
||||
}
|
||||
id := strings.TrimSpace(out)
|
||||
out, err = s.d.Cmd("logs", id)
|
||||
if err != nil {
|
||||
c.Fatalf("Logs request should be sent and then fail with \"none\" driver")
|
||||
if err == nil {
|
||||
c.Fatalf("Logs should fail with 'none' driver")
|
||||
}
|
||||
if !strings.Contains(out, `Error running logs job: Failed to get logging factory: logger: no log driver named 'none' is registered`) {
|
||||
if !strings.Contains(out, `"logs" command is supported only for "json-file" and "journald" logging drivers (got: none)`) {
|
||||
c.Fatalf("There should be an error about none not being a recognized log driver, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,3 +389,11 @@ func (s *DockerSuite) TestInspectTempateError(c *check.C) {
|
||||
c.Assert(err, check.Not(check.IsNil))
|
||||
c.Assert(out, checker.Contains, "Template parsing error")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestInspectJSONFields(c *check.C) {
|
||||
dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
|
||||
out, _, err := dockerCmdWithError("inspect", "--type=container", "--format='{{.HostConfig.Dns}}'", "busybox")
|
||||
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(out, checker.Equals, "[]\n")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions/v1p20"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/libnetwork/driverapi"
|
||||
remoteapi "github.com/docker/libnetwork/drivers/remote/api"
|
||||
"github.com/docker/libnetwork/ipamapi"
|
||||
@@ -516,6 +517,7 @@ func (s *DockerDaemonSuite) TestDockerNetworkNoDiscoveryDefaultBridgeNetwork(c *
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkAnonymousEndpoint(c *check.C) {
|
||||
hostsFile := "/etc/hosts"
|
||||
cstmBridgeNw := "custom-bridge-nw"
|
||||
cstmBridgeNw1 := "custom-bridge-nw1"
|
||||
|
||||
dockerCmd(c, "network", "create", "-d", "bridge", cstmBridgeNw)
|
||||
assertNwIsAvailable(c, cstmBridgeNw)
|
||||
@@ -539,6 +541,18 @@ func (s *DockerNetworkSuite) TestDockerNetworkAnonymousEndpoint(c *check.C) {
|
||||
c.Assert(string(hosts1), checker.Equals, string(hosts1post),
|
||||
check.Commentf("Unexpected %s change on anonymous container creation", hostsFile))
|
||||
|
||||
// Connect the 2nd container to a new network and verify the
|
||||
// first container /etc/hosts file still hasn't changed.
|
||||
dockerCmd(c, "network", "create", "-d", "bridge", cstmBridgeNw1)
|
||||
assertNwIsAvailable(c, cstmBridgeNw1)
|
||||
|
||||
dockerCmd(c, "network", "connect", cstmBridgeNw1, cid2)
|
||||
|
||||
hosts1post, err = readContainerFileWithExec(cid1, hostsFile)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(string(hosts1), checker.Equals, string(hosts1post),
|
||||
check.Commentf("Unexpected %s change on container connect", hostsFile))
|
||||
|
||||
// start a named container
|
||||
cName := "AnyName"
|
||||
out, _ = dockerCmd(c, "run", "-d", "--net", cstmBridgeNw, "--name", cName, "busybox", "top")
|
||||
@@ -725,3 +739,46 @@ func (s *DockerNetworkSuite) TestDockerNetworkMultipleNetworksUngracefulDaemonRe
|
||||
|
||||
verifyContainerIsConnectedToNetworks(c, s.d, cName, nwList)
|
||||
}
|
||||
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkRunNetByID(c *check.C) {
|
||||
out, _ := dockerCmd(c, "network", "create", "one")
|
||||
dockerCmd(c, "run", "-d", "--net", strings.TrimSpace(out), "busybox", "top")
|
||||
}
|
||||
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkConnectToHostFromOtherNetwork(c *check.C) {
|
||||
dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
|
||||
c.Assert(waitRun("container1"), check.IsNil)
|
||||
dockerCmd(c, "network", "disconnect", "bridge", "container1")
|
||||
out, _, err := dockerCmdWithError("network", "connect", "host", "container1")
|
||||
c.Assert(err, checker.NotNil, check.Commentf(out))
|
||||
c.Assert(out, checker.Contains, runconfig.ErrConflictHostNetwork.Error())
|
||||
}
|
||||
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkDisconnectFromHost(c *check.C) {
|
||||
dockerCmd(c, "run", "-d", "--name", "container1", "--net=host", "busybox", "top")
|
||||
c.Assert(waitRun("container1"), check.IsNil)
|
||||
out, _, err := dockerCmdWithError("network", "disconnect", "host", "container1")
|
||||
c.Assert(err, checker.NotNil, check.Commentf("Should err out disconnect from host"))
|
||||
c.Assert(out, checker.Contains, runconfig.ErrConflictHostNetwork.Error())
|
||||
}
|
||||
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkConnectWithPortMapping(c *check.C) {
|
||||
dockerCmd(c, "network", "create", "test1")
|
||||
dockerCmd(c, "run", "-d", "--name", "c1", "-p", "5000:5000", "busybox", "top")
|
||||
c.Assert(waitRun("c1"), check.IsNil)
|
||||
dockerCmd(c, "network", "connect", "test1", "c1")
|
||||
}
|
||||
|
||||
func (s *DockerNetworkSuite) TestDockerNetworkConnectWithMac(c *check.C) {
|
||||
macAddress := "02:42:ac:11:00:02"
|
||||
dockerCmd(c, "network", "create", "mynetwork")
|
||||
dockerCmd(c, "run", "--name=test", "-d", "--mac-address", macAddress, "busybox", "top")
|
||||
c.Assert(waitRun("test"), check.IsNil)
|
||||
mac1, err := inspectField("test", "NetworkSettings.Networks.bridge.MacAddress")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(mac1), checker.Equals, macAddress)
|
||||
dockerCmd(c, "network", "connect", "mynetwork", "test")
|
||||
mac2, err := inspectField("test", "NetworkSettings.Networks.mynetwork.MacAddress")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(mac2), checker.Not(checker.Equals), strings.TrimSpace(mac1))
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
@@ -97,6 +100,46 @@ func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPushBadParentChain tries to push an image with a corrupted parent chain
|
||||
// in the v1compatibility files, and makes sure the push process fixes it.
|
||||
func (s *DockerRegistrySuite) TestPushBadParentChain(c *check.C) {
|
||||
repoName := fmt.Sprintf("%v/dockercli/badparent", privateRegistryURL)
|
||||
|
||||
id, err := buildImage(repoName, `
|
||||
FROM busybox
|
||||
CMD echo "adding another layer"
|
||||
`, true)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
// Push to create v1compatibility file
|
||||
dockerCmd(c, "push", repoName)
|
||||
|
||||
// Corrupt the parent in the v1compatibility file from the top layer
|
||||
filename := filepath.Join(dockerBasePath, "graph", id, "v1Compatibility")
|
||||
|
||||
jsonBytes, err := ioutil.ReadFile(filename)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not read v1Compatibility file: %s", err))
|
||||
|
||||
var img image.Image
|
||||
err = json.Unmarshal(jsonBytes, &img)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not unmarshal json: %s", err))
|
||||
|
||||
img.Parent = "1234123412341234123412341234123412341234123412341234123412341234"
|
||||
|
||||
jsonBytes, err = json.Marshal(&img)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not marshal json: %s", err))
|
||||
|
||||
err = ioutil.WriteFile(filename, jsonBytes, 0600)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not write v1Compatibility file: %s", err))
|
||||
|
||||
dockerCmd(c, "push", repoName)
|
||||
|
||||
// pull should succeed
|
||||
dockerCmd(c, "pull", repoName)
|
||||
}
|
||||
|
||||
func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
|
||||
repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
|
||||
emptyTarball, err := ioutil.TempFile("", "empty_tarball")
|
||||
|
||||
@@ -1214,6 +1214,14 @@ func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (s
|
||||
}
|
||||
|
||||
func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) {
|
||||
id, _, err := buildImageFromContextWithOut(name, ctx, useCache, buildFlags...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func buildImageFromContextWithOut(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, error) {
|
||||
args := []string{"build", "-t", name}
|
||||
if !useCache {
|
||||
args = append(args, "--no-cache")
|
||||
@@ -1224,9 +1232,13 @@ func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFl
|
||||
buildCmd.Dir = ctx.Dir
|
||||
out, exitCode, err := runCommandWithOutput(buildCmd)
|
||||
if err != nil || exitCode != 0 {
|
||||
return "", fmt.Errorf("failed to build the image: %s", out)
|
||||
return "", "", fmt.Errorf("failed to build the image: %s", out)
|
||||
}
|
||||
return getIDByName(name)
|
||||
id, err := getIDByName(name)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return id, out, nil
|
||||
}
|
||||
|
||||
func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) {
|
||||
|
||||
@@ -101,6 +101,16 @@ func (opts *ListOpts) GetAll() []string {
|
||||
return (*opts.values)
|
||||
}
|
||||
|
||||
// GetAllOrEmpty returns the values of the slice
|
||||
// or an empty slice when there are no values.
|
||||
func (opts *ListOpts) GetAllOrEmpty() []string {
|
||||
v := *opts.values
|
||||
if v == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Get checks the existence of the specified key.
|
||||
func (opts *ListOpts) Get(key string) bool {
|
||||
for _, k := range *opts.values {
|
||||
|
||||
+21
-6
@@ -25,6 +25,7 @@ func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, er
|
||||
defer pools.BufioReader32KPool.Put(trBuf)
|
||||
|
||||
var dirs []*tar.Header
|
||||
unpackedPaths := make(map[string]struct{})
|
||||
|
||||
if options == nil {
|
||||
options = &TarOptions{}
|
||||
@@ -134,14 +135,27 @@ func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, er
|
||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
||||
dir := filepath.Dir(path)
|
||||
if base == WhiteoutOpaqueDir {
|
||||
fi, err := os.Lstat(dir)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := os.Mkdir(dir, fi.Mode()&os.ModePerm); err != nil {
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // parent was deleted
|
||||
}
|
||||
return err
|
||||
}
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
if _, exists := unpackedPaths[path]; !exists {
|
||||
err := os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
@@ -214,6 +228,7 @@ func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, er
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
unpackedPaths[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,14 @@ package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
func TestApplyLayerInvalidFilenames(t *testing.T) {
|
||||
@@ -188,3 +195,176 @@ func TestApplyLayerInvalidSymlink(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayerWhiteouts(t *testing.T) {
|
||||
wd, err := ioutil.TempDir("", "graphdriver-test-whiteouts")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(wd)
|
||||
|
||||
base := []string{
|
||||
".baz",
|
||||
"bar/",
|
||||
"bar/bax",
|
||||
"bar/bay/",
|
||||
"baz",
|
||||
"foo/",
|
||||
"foo/.abc",
|
||||
"foo/.bcd/",
|
||||
"foo/.bcd/a",
|
||||
"foo/cde/",
|
||||
"foo/cde/def",
|
||||
"foo/cde/efg",
|
||||
"foo/fgh",
|
||||
"foobar",
|
||||
}
|
||||
|
||||
type tcase struct {
|
||||
change, expected []string
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{
|
||||
base,
|
||||
base,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
".bay",
|
||||
".wh.baz",
|
||||
"foo/",
|
||||
"foo/.bce",
|
||||
"foo/.wh..wh..opq",
|
||||
"foo/cde/",
|
||||
"foo/cde/efg",
|
||||
},
|
||||
[]string{
|
||||
".bay",
|
||||
".baz",
|
||||
"bar/",
|
||||
"bar/bax",
|
||||
"bar/bay/",
|
||||
"foo/",
|
||||
"foo/.bce",
|
||||
"foo/cde/",
|
||||
"foo/cde/efg",
|
||||
"foobar",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
".bay",
|
||||
".wh..baz",
|
||||
".wh.foobar",
|
||||
"foo/",
|
||||
"foo/.abc",
|
||||
"foo/.wh.cde",
|
||||
"bar/",
|
||||
},
|
||||
[]string{
|
||||
".bay",
|
||||
"bar/",
|
||||
"bar/bax",
|
||||
"bar/bay/",
|
||||
"foo/",
|
||||
"foo/.abc",
|
||||
"foo/.bce",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
".abc",
|
||||
".wh..wh..opq",
|
||||
"foobar",
|
||||
},
|
||||
[]string{
|
||||
".abc",
|
||||
"foobar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tcases {
|
||||
l, err := makeTestLayer(tc.change)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = UnpackLayer(wd, l, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
paths, err := readDirContents(wd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.expected, paths) {
|
||||
t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeTestLayer(paths []string) (rc io.ReadCloser, err error) {
|
||||
tmpDir, err := ioutil.TempDir("", "graphdriver-test-mklayer")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
}
|
||||
}()
|
||||
for _, p := range paths {
|
||||
if p[len(p)-1] == filepath.Separator {
|
||||
if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = ioutil.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
archive, err := Tar(tmpDir, Uncompressed)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
os.RemoveAll(tmpDir)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func readDirContents(root string) ([]string, error) {
|
||||
var files []string
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path == root {
|
||||
return nil
|
||||
}
|
||||
rel, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
rel = rel + "/"
|
||||
}
|
||||
files = append(files, rel)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
+51
-10
@@ -1,32 +1,54 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WriteFlusher wraps the Write and Flush operation.
|
||||
// WriteFlusher wraps the Write and Flush operation ensuring that every write
|
||||
// is a flush. In addition, the Close method can be called to intercept
|
||||
// Read/Write calls if the targets lifecycle has already ended.
|
||||
type WriteFlusher struct {
|
||||
sync.Mutex
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
flusher http.Flusher
|
||||
flushed bool
|
||||
closed error
|
||||
|
||||
// TODO(stevvooe): Use channel for closed instead, remove mutex. Using a
|
||||
// channel will allow one to properly order the operations.
|
||||
}
|
||||
|
||||
var errWriteFlusherClosed = errors.New("writeflusher: closed")
|
||||
|
||||
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
||||
wf.Lock()
|
||||
defer wf.Unlock()
|
||||
wf.mu.Lock()
|
||||
defer wf.mu.Unlock()
|
||||
if wf.closed != nil {
|
||||
return 0, wf.closed
|
||||
}
|
||||
|
||||
n, err = wf.w.Write(b)
|
||||
wf.flushed = true
|
||||
wf.flusher.Flush()
|
||||
wf.flush() // every write is a flush.
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Flush the stream immediately.
|
||||
func (wf *WriteFlusher) Flush() {
|
||||
wf.Lock()
|
||||
defer wf.Unlock()
|
||||
wf.mu.Lock()
|
||||
defer wf.mu.Unlock()
|
||||
|
||||
wf.flush()
|
||||
}
|
||||
|
||||
// flush the stream immediately without taking a lock. Used internally.
|
||||
func (wf *WriteFlusher) flush() {
|
||||
if wf.closed != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wf.flushed = true
|
||||
wf.flusher.Flush()
|
||||
}
|
||||
@@ -34,11 +56,30 @@ func (wf *WriteFlusher) Flush() {
|
||||
// Flushed returns the state of flushed.
|
||||
// If it's flushed, return true, or else it return false.
|
||||
func (wf *WriteFlusher) Flushed() bool {
|
||||
wf.Lock()
|
||||
defer wf.Unlock()
|
||||
// BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
|
||||
// be used to detect whether or a response code has been issued or not.
|
||||
// Another hook should be used instead.
|
||||
wf.mu.Lock()
|
||||
defer wf.mu.Unlock()
|
||||
|
||||
return wf.flushed
|
||||
}
|
||||
|
||||
// Close closes the write flusher, disallowing any further writes to the
|
||||
// target. After the flusher is closed, all calls to write or flush will
|
||||
// result in an error.
|
||||
func (wf *WriteFlusher) Close() error {
|
||||
wf.mu.Lock()
|
||||
defer wf.mu.Unlock()
|
||||
|
||||
if wf.closed != nil {
|
||||
return wf.closed
|
||||
}
|
||||
|
||||
wf.closed = errWriteFlusherClosed
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewWriteFlusher returns a new WriteFlusher.
|
||||
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||
var flusher http.Flusher
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -14,13 +15,23 @@ var (
|
||||
|
||||
// file to check to determine Operating System
|
||||
etcOsRelease = "/etc/os-release"
|
||||
|
||||
// used by stateless systems like Clear Linux
|
||||
altEtcOSRelease = "/usr/lib/os-release"
|
||||
)
|
||||
|
||||
// GetOperatingSystem gets the name of the current operating system.
|
||||
func GetOperatingSystem() (string, error) {
|
||||
b, err := ioutil.ReadFile(etcOsRelease)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if _, err2 := os.Stat(altEtcOSRelease); err2 == nil {
|
||||
b, err2 = ioutil.ReadFile(altEtcOSRelease)
|
||||
if err2 != nil {
|
||||
return "", err2
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if i := bytes.Index(b, []byte("PRETTY_NAME")); i >= 0 {
|
||||
b = b[i+13:]
|
||||
|
||||
@@ -266,7 +266,8 @@ installed and available at runtime:
|
||||
|
||||
* iptables version 1.4 or later
|
||||
* procps (or similar provider of a "ps" executable)
|
||||
* e2fsprogs version 1.4.12 or later (in use: mkfs.ext4, mkfs.xfs, tune2fs)
|
||||
* e2fsprogs version 1.4.12 or later (in use: mkfs.ext4, tune2fs)
|
||||
* xfsprogs (in use: mkfs.xfs)
|
||||
* XZ Utils version 4.9 or later
|
||||
* a [properly
|
||||
mounted](https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount)
|
||||
|
||||
+53
-24
@@ -239,15 +239,28 @@ func validateNoSchema(reposName string) error {
|
||||
|
||||
// ValidateRepositoryName validates a repository name
|
||||
func ValidateRepositoryName(reposName string) error {
|
||||
var err error
|
||||
if err = validateNoSchema(reposName); err != nil {
|
||||
return err
|
||||
_, _, err := loadRepositoryName(reposName, true)
|
||||
return err
|
||||
}
|
||||
|
||||
// loadRepositoryName returns the repo name splitted into index name
|
||||
// and remote repo name. It returns an error if the name is not valid.
|
||||
func loadRepositoryName(reposName string, checkRemoteName bool) (string, string, error) {
|
||||
if err := validateNoSchema(reposName); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
indexName, remoteName := splitReposName(reposName)
|
||||
if _, err = ValidateIndexName(indexName); err != nil {
|
||||
return err
|
||||
|
||||
var err error
|
||||
if indexName, err = ValidateIndexName(indexName); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return validateRemoteName(remoteName)
|
||||
if checkRemoteName {
|
||||
if err = validateRemoteName(remoteName); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
return indexName, remoteName, nil
|
||||
}
|
||||
|
||||
// NewIndexInfo returns IndexInfo configuration from indexName
|
||||
@@ -301,34 +314,22 @@ func splitReposName(reposName string) (string, string) {
|
||||
|
||||
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
|
||||
func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) (*RepositoryInfo, error) {
|
||||
if err := validateNoSchema(reposName); err != nil {
|
||||
indexName, remoteName, err := loadRepositoryName(reposName, !bySearch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexName, remoteName := splitReposName(reposName)
|
||||
|
||||
if !bySearch {
|
||||
if err := validateRemoteName(remoteName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
repoInfo := &RepositoryInfo{
|
||||
RemoteName: remoteName,
|
||||
}
|
||||
|
||||
var err error
|
||||
repoInfo.Index, err = config.NewIndexInfo(indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if repoInfo.Index.Official {
|
||||
normalizedName := repoInfo.RemoteName
|
||||
if strings.HasPrefix(normalizedName, "library/") {
|
||||
// If pull "library/foo", it's stored locally under "foo"
|
||||
normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
|
||||
}
|
||||
normalizedName := normalizeLibraryRepoName(repoInfo.RemoteName)
|
||||
|
||||
repoInfo.LocalName = normalizedName
|
||||
repoInfo.RemoteName = normalizedName
|
||||
@@ -342,7 +343,7 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool)
|
||||
|
||||
repoInfo.CanonicalName = "docker.io/" + repoInfo.RemoteName
|
||||
} else {
|
||||
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
|
||||
repoInfo.LocalName = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName)
|
||||
repoInfo.CanonicalName = repoInfo.LocalName
|
||||
|
||||
}
|
||||
@@ -378,10 +379,38 @@ func ParseIndexInfo(reposName string) (*IndexInfo, error) {
|
||||
|
||||
// NormalizeLocalName transforms a repository name into a normalize LocalName
|
||||
// Passes through the name without transformation on error (image id, etc)
|
||||
// It does not use the repository info because we don't want to load
|
||||
// the repository index and do request over the network.
|
||||
func NormalizeLocalName(name string) string {
|
||||
repoInfo, err := ParseRepositoryInfo(name)
|
||||
indexName, remoteName, err := loadRepositoryName(name, true)
|
||||
if err != nil {
|
||||
return name
|
||||
}
|
||||
return repoInfo.LocalName
|
||||
|
||||
var officialIndex bool
|
||||
// Return any configured index info, first.
|
||||
if index, ok := emptyServiceConfig.IndexConfigs[indexName]; ok {
|
||||
officialIndex = index.Official
|
||||
}
|
||||
|
||||
if officialIndex {
|
||||
return normalizeLibraryRepoName(remoteName)
|
||||
}
|
||||
return localNameFromRemote(indexName, remoteName)
|
||||
}
|
||||
|
||||
// normalizeLibraryRepoName removes the library prefix from
|
||||
// the repository name for official repos.
|
||||
func normalizeLibraryRepoName(name string) string {
|
||||
if strings.HasPrefix(name, "library/") {
|
||||
// If pull "library/foo", it's stored locally under "foo"
|
||||
name = strings.SplitN(name, "/", 2)[1]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// localNameFromRemote combines the index name and the repo remote name
|
||||
// to generate a repo local name.
|
||||
func localNameFromRemote(indexName, remoteName string) string {
|
||||
return indexName + "/" + remoteName
|
||||
}
|
||||
|
||||
+27
-20
@@ -21,6 +21,8 @@ var (
|
||||
ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: --net=<NETWORK> can't be used with links. This would result in undefined behavior")
|
||||
// ErrConflictSharedNetwork conflict between private and other networks
|
||||
ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
|
||||
// ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network.
|
||||
ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
|
||||
// ErrConflictNoNetwork conflict between private and other networks
|
||||
ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in --none mode")
|
||||
// ErrConflictNetworkAndDNS conflict between --dns and the network mode
|
||||
@@ -361,26 +363,31 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks.GetAll(),
|
||||
PublishAllPorts: *flPublishAll,
|
||||
DNS: flDNS.GetAll(),
|
||||
DNSSearch: flDNSSearch.GetAll(),
|
||||
DNSOptions: flDNSOptions.GetAll(),
|
||||
ExtraHosts: flExtraHosts.GetAll(),
|
||||
VolumesFrom: flVolumesFrom.GetAll(),
|
||||
NetworkMode: NetworkMode(*flNetMode),
|
||||
IpcMode: ipcMode,
|
||||
PidMode: pidMode,
|
||||
UTSMode: utsMode,
|
||||
Devices: deviceMappings,
|
||||
CapAdd: stringutils.NewStrSlice(flCapAdd.GetAll()...),
|
||||
CapDrop: stringutils.NewStrSlice(flCapDrop.GetAll()...),
|
||||
GroupAdd: flGroupAdd.GetAll(),
|
||||
RestartPolicy: restartPolicy,
|
||||
SecurityOpt: flSecurityOpt.GetAll(),
|
||||
ReadonlyRootfs: *flReadonlyRootfs,
|
||||
Ulimits: flUlimits.GetList(),
|
||||
LogConfig: LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
|
||||
CgroupParent: *flCgroupParent,
|
||||
VolumeDriver: *flVolumeDriver,
|
||||
// Make sure the dns fields are never nil.
|
||||
// New containers don't ever have those fields nil,
|
||||
// but pre created containers can still have those nil values.
|
||||
// See https://github.com/docker/docker/pull/17779
|
||||
// for a more detailed explanation on why we don't want that.
|
||||
DNS: flDNS.GetAllOrEmpty(),
|
||||
DNSSearch: flDNSSearch.GetAllOrEmpty(),
|
||||
DNSOptions: flDNSOptions.GetAllOrEmpty(),
|
||||
ExtraHosts: flExtraHosts.GetAll(),
|
||||
VolumesFrom: flVolumesFrom.GetAll(),
|
||||
NetworkMode: NetworkMode(*flNetMode),
|
||||
IpcMode: ipcMode,
|
||||
PidMode: pidMode,
|
||||
UTSMode: utsMode,
|
||||
Devices: deviceMappings,
|
||||
CapAdd: stringutils.NewStrSlice(flCapAdd.GetAll()...),
|
||||
CapDrop: stringutils.NewStrSlice(flCapDrop.GetAll()...),
|
||||
GroupAdd: flGroupAdd.GetAll(),
|
||||
RestartPolicy: restartPolicy,
|
||||
SecurityOpt: flSecurityOpt.GetAll(),
|
||||
ReadonlyRootfs: *flReadonlyRootfs,
|
||||
Ulimits: flUlimits.GetList(),
|
||||
LogConfig: LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
|
||||
CgroupParent: *flCgroupParent,
|
||||
VolumeDriver: *flVolumeDriver,
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
|
||||
+1
-4
@@ -128,14 +128,11 @@ type ipamData struct {
|
||||
|
||||
type driverTable map[string]*driverData
|
||||
|
||||
//type networkTable map[string]*network
|
||||
//type endpointTable map[string]*endpoint
|
||||
type ipamTable map[string]*ipamData
|
||||
type sandboxTable map[string]*sandbox
|
||||
|
||||
type controller struct {
|
||||
id string
|
||||
//networks networkTable
|
||||
id string
|
||||
drivers driverTable
|
||||
ipamDrivers ipamTable
|
||||
sandboxes sandboxTable
|
||||
|
||||
@@ -98,6 +98,7 @@ type bridgeNetwork struct {
|
||||
endpoints map[string]*bridgeEndpoint // key: endpoint id
|
||||
portMapper *portmapper.PortMapper
|
||||
driver *driver // The network's driver
|
||||
veth *netlink.Veth
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
@@ -450,6 +451,8 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d
|
||||
}
|
||||
|
||||
if len(ipamV6Data) > 0 {
|
||||
c.AddressIPv6 = ipamV6Data[0].Pool
|
||||
|
||||
if ipamV6Data[0].Gateway != nil {
|
||||
c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway)
|
||||
}
|
||||
@@ -738,7 +741,9 @@ func (d *driver) DeleteNetwork(nid string) error {
|
||||
|
||||
// We only delete the bridge when it's not the default bridge. This is keep the backward compatible behavior.
|
||||
if !config.DefaultBridge {
|
||||
err = netlink.LinkDel(n.bridge.Link)
|
||||
if err := netlink.LinkDel(n.bridge.Link); err != nil {
|
||||
logrus.Warnf("Failed to remove bridge interface %s on network %s delete: %v", config.BridgeName, nid, err)
|
||||
}
|
||||
}
|
||||
|
||||
return d.storeDelete(config)
|
||||
@@ -870,11 +875,13 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Warnf("network %v", n)
|
||||
logrus.Warnf("veth %v", n.veth)
|
||||
// Generate and add the interface pipe host <-> sandbox
|
||||
veth := &netlink.Veth{
|
||||
n.veth = &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{Name: hostIfName, TxQLen: 0},
|
||||
PeerName: containerIfName}
|
||||
if err = netlink.LinkAdd(veth); err != nil {
|
||||
if err = netlink.LinkAdd(n.veth); err != nil {
|
||||
return types.InternalErrorf("failed to add the host (%s) <=> sandbox (%s) pair interfaces: %v", hostIfName, containerIfName, err)
|
||||
}
|
||||
|
||||
@@ -959,13 +966,20 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
|
||||
if endpoint.addrv6 == nil && config.EnableIPv6 {
|
||||
var ip6 net.IP
|
||||
network := n.bridge.bridgeIPv6
|
||||
if config.AddressIPv6 != nil {
|
||||
network = config.AddressIPv6
|
||||
}
|
||||
|
||||
ones, _ := network.Mask.Size()
|
||||
if ones <= 80 {
|
||||
ip6 = make(net.IP, len(network.IP))
|
||||
copy(ip6, network.IP)
|
||||
for i, h := range endpoint.macAddress {
|
||||
ip6[i+10] = h
|
||||
}
|
||||
if ones > 80 {
|
||||
err = types.ForbiddenErrorf("Cannot self generate an IPv6 address on network %v: At least 48 host bits are needed.", network)
|
||||
return err
|
||||
}
|
||||
|
||||
ip6 = make(net.IP, len(network.IP))
|
||||
copy(ip6, network.IP)
|
||||
for i, h := range endpoint.macAddress {
|
||||
ip6[i+10] = h
|
||||
}
|
||||
|
||||
endpoint.addrv6 = &net.IPNet{IP: ip6, Mask: network.Mask}
|
||||
@@ -1037,9 +1051,8 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
|
||||
// Remove port mappings. Do not stop endpoint delete on unmap failure
|
||||
n.releasePorts(ep)
|
||||
|
||||
// Try removal of link. Discard error: link pair might have
|
||||
// already been deleted by sandbox delete. Make sure defer
|
||||
// does not see this error either.
|
||||
// Try removal of link. Discard error: it is a best effort.
|
||||
// Also make sure defer does not see this error either.
|
||||
if link, err := netlink.LinkByName(ep.srcName); err == nil {
|
||||
netlink.LinkDel(link)
|
||||
}
|
||||
@@ -1100,6 +1113,11 @@ func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, erro
|
||||
m[netlabel.MacAddress] = ep.macAddress
|
||||
}
|
||||
|
||||
// Add details of the bridge
|
||||
m[netlabel.BridgeName] = n.config.BridgeName
|
||||
m[netlabel.BridgeLinkName] = n.veth.LinkAttrs.Name
|
||||
m[netlabel.BridgeEID] = eid
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -584,7 +584,8 @@ func (ep *endpoint) Delete() error {
|
||||
ep.Lock()
|
||||
epid := ep.id
|
||||
name := ep.name
|
||||
if ep.sandboxID != "" {
|
||||
sb, _ := n.getController().SandboxByID(ep.sandboxID)
|
||||
if sb != nil {
|
||||
ep.Unlock()
|
||||
return &ActiveContainerError{name: name, id: epid}
|
||||
}
|
||||
@@ -737,7 +738,7 @@ func (ep *endpoint) DataScope() string {
|
||||
return ep.getNetwork().DataScope()
|
||||
}
|
||||
|
||||
func (ep *endpoint) assignAddress() error {
|
||||
func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
|
||||
var (
|
||||
ipam ipamapi.Ipam
|
||||
err error
|
||||
@@ -754,11 +755,18 @@ func (ep *endpoint) assignAddress() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ep.assignAddressVersion(4, ipam)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if assignIPv4 {
|
||||
if err = ep.assignAddressVersion(4, ipam); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ep.assignAddressVersion(6, ipam)
|
||||
|
||||
if assignIPv6 {
|
||||
err = ep.assignAddressVersion(6, ipam)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
||||
@@ -787,7 +795,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
||||
}
|
||||
|
||||
for _, d := range ipInfo {
|
||||
addr, _, err := ipam.RequestAddress(d.PoolID, nil, nil)
|
||||
var prefIP net.IP
|
||||
if *address != nil {
|
||||
prefIP = (*address).IP
|
||||
}
|
||||
addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, nil)
|
||||
if err == nil {
|
||||
ep.Lock()
|
||||
*address = addr
|
||||
|
||||
@@ -220,7 +220,7 @@ func (a *Allocator) parsePoolRequest(addressSpace, pool, subPool string, v6 bool
|
||||
return nil, nil, nil, ipamapi.ErrInvalidPool
|
||||
}
|
||||
if subPool != "" {
|
||||
if ipr, err = getAddressRange(subPool); err != nil {
|
||||
if ipr, err = getAddressRange(subPool, nw); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
@@ -431,9 +431,6 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
||||
aSpace.Unlock()
|
||||
|
||||
mask := p.Pool.Mask
|
||||
if p.Range != nil {
|
||||
mask = p.Range.Sub.Mask
|
||||
}
|
||||
|
||||
h, err := types.GetHostPartIP(address, mask)
|
||||
if err != nil {
|
||||
@@ -471,7 +468,6 @@ func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddres
|
||||
ordinal = ipToUint64(types.GetMinimalIP(hostPart))
|
||||
err = bitmask.Set(ordinal)
|
||||
} else {
|
||||
base.IP = ipr.Sub.IP
|
||||
ordinal, err = bitmask.SetAnyInRange(ipr.Start, ipr.End)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
+3
-3
@@ -15,12 +15,12 @@ const (
|
||||
v6 = 6
|
||||
)
|
||||
|
||||
func getAddressRange(pool string) (*AddressRange, error) {
|
||||
func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
|
||||
ip, nw, err := net.ParseCIDR(pool)
|
||||
if err != nil {
|
||||
return nil, ipamapi.ErrInvalidSubPool
|
||||
}
|
||||
lIP, e := types.GetHostPartIP(nw.IP, nw.Mask)
|
||||
lIP, e := types.GetHostPartIP(nw.IP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's lowest ip address: %v", e)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func getAddressRange(pool string) (*AddressRange, error) {
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's broadcast ip address: %v", e)
|
||||
}
|
||||
hIP, e := types.GetHostPartIP(bIP, nw.Mask)
|
||||
hIP, e := types.GetHostPartIP(bIP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,22 @@ const (
|
||||
// DriverMTU constant represents the MTU size for the network driver
|
||||
DriverMTU = DriverPrefix + ".mtu"
|
||||
|
||||
// BridgeName constant represents the name of the network bridge
|
||||
BridgeName = "io.docker.network.bridge.name"
|
||||
|
||||
// BridgeEID is the endpoint ID for the bridge
|
||||
BridgeEID = "io.docker.network.bridge.endpoint_id"
|
||||
|
||||
// BridgeLinkName constant represents the interface name created on
|
||||
// the host side.
|
||||
BridgeLinkName = "io.docker.network.bridge.linkname"
|
||||
|
||||
// KVProvider constant represents the KV provider backend
|
||||
KVProvider = DriverPrefix + ".kv_provider"
|
||||
|
||||
// KVProviderURL constant represents the KV provider URL
|
||||
KVProviderURL = DriverPrefix + ".kv_provider_url"
|
||||
|
||||
// OverlayBindInterface constant represents overlay driver bind interface
|
||||
OverlayBindInterface = DriverPrefix + ".overlay.bind_interface"
|
||||
|
||||
|
||||
+21
-1
@@ -152,6 +152,7 @@ type network struct {
|
||||
ipamV4Info []*IpamInfo
|
||||
ipamV6Info []*IpamInfo
|
||||
enableIPv6 bool
|
||||
postIPv6 bool
|
||||
epCnt *endpointCnt
|
||||
generic options.Generic
|
||||
dbIndex uint64
|
||||
@@ -298,6 +299,7 @@ func (n *network) CopyTo(o datastore.KVObject) error {
|
||||
dstN.ipamType = n.ipamType
|
||||
dstN.enableIPv6 = n.enableIPv6
|
||||
dstN.persist = n.persist
|
||||
dstN.postIPv6 = n.postIPv6
|
||||
dstN.dbIndex = n.dbIndex
|
||||
dstN.dbExists = n.dbExists
|
||||
dstN.drvOnce = n.drvOnce
|
||||
@@ -358,6 +360,7 @@ func (n *network) MarshalJSON() ([]byte, error) {
|
||||
netMap["generic"] = n.generic
|
||||
}
|
||||
netMap["persist"] = n.persist
|
||||
netMap["postIPv6"] = n.postIPv6
|
||||
if len(n.ipamV4Config) > 0 {
|
||||
ics, err := json.Marshal(n.ipamV4Config)
|
||||
if err != nil {
|
||||
@@ -418,6 +421,9 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
|
||||
if v, ok := netMap["persist"]; ok {
|
||||
n.persist = v.(bool)
|
||||
}
|
||||
if v, ok := netMap["postIPv6"]; ok {
|
||||
n.postIPv6 = v.(bool)
|
||||
}
|
||||
if v, ok := netMap["ipamType"]; ok {
|
||||
n.ipamType = v.(string)
|
||||
} else {
|
||||
@@ -505,6 +511,16 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created
|
||||
// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address
|
||||
// to a container as combination of fixed-cidr-v6 + mac-address
|
||||
// TODO: Remove this option setter once we support endpoint ipam options
|
||||
func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
|
||||
return func(n *network) {
|
||||
n.postIPv6 = enable
|
||||
}
|
||||
}
|
||||
|
||||
func (n *network) processOptions(options ...NetworkOption) {
|
||||
for _, opt := range options {
|
||||
if opt != nil {
|
||||
@@ -655,7 +671,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
||||
|
||||
ep.processOptions(options...)
|
||||
|
||||
if err = ep.assignAddress(); err != nil {
|
||||
if err = ep.assignAddress(true, !n.postIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
@@ -675,6 +691,10 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
||||
}
|
||||
}()
|
||||
|
||||
if err = ep.assignAddress(false, n.postIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = n.getController().updateToStore(ep); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+25
-9
@@ -177,13 +177,18 @@ func (sb *sandbox) Delete() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ep.Leave(sb); err != nil {
|
||||
// Retain the sanbdox if we can't obtain the network from store.
|
||||
if _, err := c.getNetworkFromStore(ep.getNetwork().ID()); err != nil {
|
||||
retain = true
|
||||
log.Warnf("Failed getting network for ep %s during sandbox %s delete: %v", ep.ID(), sb.ID(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ep.Leave(sb); err != nil {
|
||||
log.Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
|
||||
}
|
||||
|
||||
if err := ep.Delete(); err != nil {
|
||||
retain = true
|
||||
log.Warnf("Failed deleting endpoint %s: %v\n", ep.ID(), err)
|
||||
}
|
||||
}
|
||||
@@ -455,7 +460,7 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
|
||||
i := ep.iface
|
||||
ep.Unlock()
|
||||
|
||||
if i.srcName != "" {
|
||||
if i != nil && i.srcName != "" {
|
||||
var ifaceOptions []osl.IfaceOption
|
||||
|
||||
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
|
||||
@@ -951,6 +956,11 @@ func OptionGeneric(generic map[string]interface{}) SandboxOption {
|
||||
func (eh epHeap) Len() int { return len(eh) }
|
||||
|
||||
func (eh epHeap) Less(i, j int) bool {
|
||||
var (
|
||||
cip, cjp int
|
||||
ok bool
|
||||
)
|
||||
|
||||
ci, _ := eh[i].getSandbox()
|
||||
cj, _ := eh[j].getSandbox()
|
||||
|
||||
@@ -965,14 +975,20 @@ func (eh epHeap) Less(i, j int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
cip, ok := ci.epPriority[eh[i].ID()]
|
||||
if !ok {
|
||||
cip = 0
|
||||
if ci != nil {
|
||||
cip, ok = ci.epPriority[eh[i].ID()]
|
||||
if !ok {
|
||||
cip = 0
|
||||
}
|
||||
}
|
||||
cjp, ok := cj.epPriority[eh[j].ID()]
|
||||
if !ok {
|
||||
cjp = 0
|
||||
|
||||
if cj != nil {
|
||||
cjp, ok = cj.epPriority[eh[j].ID()]
|
||||
if !ok {
|
||||
cjp = 0
|
||||
}
|
||||
}
|
||||
|
||||
if cip == cjp {
|
||||
return eh[i].network.Name() < eh[j].network.Name()
|
||||
}
|
||||
|
||||
@@ -176,7 +176,6 @@ func (n *network) getEndpointsFromStore() ([]*endpoint, error) {
|
||||
|
||||
for _, kvo := range kvol {
|
||||
ep := kvo.(*endpoint)
|
||||
ep.network = n
|
||||
epl = append(epl, ep)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@ func Validate(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(label string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(label string) bool {
|
||||
return false
|
||||
|
||||
@@ -181,6 +181,11 @@ func Validate(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(label string) bool {
|
||||
return strings.Contains(label, "z") || strings.Contains(label, "Z")
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(label string) bool {
|
||||
return strings.Contains(label, "z")
|
||||
|
||||
Reference in New Issue
Block a user