func TestVersionMiddlewareWithErrors(t *testing.T) { handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if ctx.Version() == "" { t.Fatalf("Expected version, got empty string") } return nil } h := versionMiddleware(handler) req, _ := http.NewRequest("GET", "/containers/json", nil) resp := httptest.NewRecorder() ctx := context.Background() vars := map[string]string{"version": "0.1"} err := h(ctx, resp, req, vars) if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion { t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err) } vars["version"] = "100000" err = h(ctx, resp, req, vars) if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion { t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err) } }
func TestMiddlewares(t *testing.T) { cfg := &Config{} srv := &Server{ cfg: cfg, } req, _ := http.NewRequest("GET", "/containers/json", nil) resp := httptest.NewRecorder() ctx := context.Background() localHandler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if ctx.Version() == "" { t.Fatalf("Expected version, got empty string") } if ctx.RequestID() == "" { t.Fatalf("Expected request-id, got empty string") } return nil } handlerFunc := srv.handleWithGlobalMiddlewares(localHandler) if err := handlerFunc(ctx, resp, req, map[string]string{}); err != nil { t.Fatal(err) } }
func TestEventsLog(t *testing.T) { ctx := context.Background() e := New() _, l1 := e.Subscribe() _, l2 := e.Subscribe() defer e.Evict(l1) defer e.Evict(l2) count := e.SubscribersCount() if count != 2 { t.Fatalf("Must be 2 subscribers, got %d", count) } e.Log(ctx, "test", "cont", "image") select { case msg := <-l1: jmsg, ok := msg.(*jsonmessage.JSONMessage) if !ok { t.Fatalf("Unexpected type %T", msg) } if len(e.events) != 1 { t.Fatalf("Must be only one event, got %d", len(e.events)) } if jmsg.Status != "test" { t.Fatalf("Status should be test, got %s", jmsg.Status) } if jmsg.ID != "cont" { t.Fatalf("ID should be cont, got %s", jmsg.ID) } if jmsg.From != "image" { t.Fatalf("From should be image, got %s", jmsg.From) } case <-time.After(1 * time.Second): t.Fatal("Timeout waiting for broadcasted message") } select { case msg := <-l2: jmsg, ok := msg.(*jsonmessage.JSONMessage) if !ok { t.Fatalf("Unexpected type %T", msg) } if len(e.events) != 1 { t.Fatalf("Must be only one event, got %d", len(e.events)) } if jmsg.Status != "test" { t.Fatalf("Status should be test, got %s", jmsg.Status) } if jmsg.ID != "cont" { t.Fatalf("ID should be cont, got %s", jmsg.ID) } if jmsg.From != "image" { t.Fatalf("From should be image, got %s", jmsg.From) } case <-time.After(1 * time.Second): t.Fatal("Timeout waiting for broadcasted message") } }
func TestLogEvents(t *testing.T) { ctx := context.Background() e := New() for i := 0; i < eventsLimit+16; i++ { action := fmt.Sprintf("action_%d", i) id := fmt.Sprintf("cont_%d", i) from := fmt.Sprintf("image_%d", i) e.Log(ctx, action, id, from) } time.Sleep(50 * time.Millisecond) current, l := e.Subscribe() for i := 0; i < 10; i++ { num := i + eventsLimit + 16 action := fmt.Sprintf("action_%d", num) id := fmt.Sprintf("cont_%d", num) from := fmt.Sprintf("image_%d", num) e.Log(ctx, action, id, from) } if len(e.events) != eventsLimit { t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events)) } var msgs []*jsonmessage.JSONMessage for len(msgs) < 10 { m := <-l jm, ok := (m).(*jsonmessage.JSONMessage) if !ok { t.Fatalf("Unexpected type %T", m) } msgs = append(msgs, jm) } if len(current) != eventsLimit { t.Fatalf("Must be %d events, got %d", eventsLimit, len(current)) } first := current[0] if first.Status != "action_16" { t.Fatalf("First action is %s, must be action_16", first.Status) } last := current[len(current)-1] if last.Status != "action_79" { t.Fatalf("Last action is %s, must be action_79", last.Status) } firstC := msgs[0] if firstC.Status != "action_80" { t.Fatalf("First action is %s, must be action_80", firstC.Status) } lastC := msgs[len(msgs)-1] if lastC.Status != "action_89" { t.Fatalf("Last action is %s, must be action_89", lastC.Status) } }
// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case // d.Shutdown() is waiting too long to kill container or worst it's // blocked there func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { ch := make(chan struct{}) go func() { d.Shutdown(context.Background()) close(ch) }() select { case <-ch: logrus.Debug("Clean shutdown succeeded") case <-time.After(timeout * time.Second): logrus.Error("Force shutdown daemon") } }
func TestRequestIDMiddleware(t *testing.T) { handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if ctx.RequestID() == "" { t.Fatalf("Expected request-id, got empty string") } return nil } h := requestIDMiddleware(handler) req, _ := http.NewRequest("GET", "/containers/json", nil) resp := httptest.NewRecorder() ctx := context.Background() if err := h(ctx, resp, req, map[string]string{}); err != nil { t.Fatal(err) } }
func TestEventsLogTimeout(t *testing.T) { ctx := context.Background() e := New() _, l := e.Subscribe() defer e.Evict(l) c := make(chan struct{}) go func() { e.Log(ctx, "test", "cont", "image") close(c) }() select { case <-c: case <-time.After(time.Second): t.Fatal("Timeout publishing message") } }
func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHandler HTTPAPIFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the handler generation logrus.Debugf("Calling %s %s", localMethod, localRoute) // Define the context that we'll pass around to share info // like the docker-request-id. // // The 'context' will be used for global data that should // apply to all requests. Data that is specific to the // immediate function being called should still be passed // as 'args' on the function call. ctx := context.Background() handlerFunc := s.handleWithGlobalMiddlewares(localHandler) if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil { logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, utils.GetErrorMessage(err)) httpError(w, err) } } }
func makeHTTPHandler(logging bool, localMethod string, localRoute string, handlerFunc HTTPAPIFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Define the context that we'll pass around to share info // like the docker-request-id. // // The 'context' will be used for global data that should // apply to all requests. Data that is specific to the // immediate function being called should still be passed // as 'args' on the function call. reqID := stringid.TruncateID(stringid.GenerateNonCryptoID()) apiVersion := version.Version(mux.Vars(r)["version"]) if apiVersion == "" { apiVersion = api.Version } ctx := context.Background() ctx = context.WithValue(ctx, context.RequestID, reqID) ctx = context.WithValue(ctx, context.APIVersion, apiVersion) // log the request logrus.Debugf("Calling %s %s", localMethod, localRoute) if logging { logrus.Infof("%s %s", r.Method, r.RequestURI) } if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") // v1.20 onwards includes the GOOS of the client after the version // such as Docker/1.7.0 (linux) if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") { userAgent[1] = strings.Split(userAgent[1], " ")[0] } if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } if corsHeaders != "" { writeCorsHeaders(w, r, corsHeaders) } if apiVersion.GreaterThan(api.Version) { http.Error(w, fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, api.Version).Error(), http.StatusBadRequest) return } if apiVersion.LessThan(api.MinVersion) { http.Error(w, fmt.Errorf("client is too old, minimum supported API version is %s, please upgrade your client to a newer version", api.MinVersion).Error(), http.StatusBadRequest) return } w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")") if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil { logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) httpError(w, err) } } }
// NewDaemon sets up everything for the daemon to be able to service // requests from the webserver. func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) { setDefaultMtu(config) // Ensure we have compatible configuration options if err := checkConfigOptions(config); err != nil { return nil, err } // Do we have a disabled network? config.DisableBridge = isBridgeNetworkDisabled(config) // Verify the platform is supported as a daemon if !platformSupported { return nil, errSystemNotSupported } // Validate platform-specific requirements if err := checkSystem(); err != nil { return nil, err } // set up SIGUSR1 handler on Unix-like systems, or a Win32 global event // on Windows to dump Go routine stacks setupDumpStackTrap() // get the canonical path to the Docker root directory var realRoot string if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) { realRoot = config.Root } else { realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root) if err != nil { return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err) } } config.Root = realRoot // Create the root directory if it doesn't exists if err := system.MkdirAll(config.Root, 0700); err != nil { return nil, err } // set up the tmpDir to use a canonical path tmp, err := tempDir(config.Root) if err != nil { return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err) } realTmp, err := fileutils.ReadSymlinkedDirectory(tmp) if err != nil { return nil, fmt.Errorf("Unable to get the full path to the TempDir (%s): %s", tmp, err) } os.Setenv("TMPDIR", realTmp) // Set the default driver graphdriver.DefaultDriver = config.GraphDriver // Load storage driver driver, err := graphdriver.New(config.Root, config.GraphOptions) if err != nil { return nil, fmt.Errorf("error initializing graphdriver: %v", err) } logrus.Debugf("Using graph driver %s", driver) d := &Daemon{} d.driver = driver // Ensure the graph driver is shutdown at a later point defer func() { if err != nil { if err := d.Shutdown(context.Background()); err != nil { logrus.Error(err) } } }() // Verify logging driver type if config.LogConfig.Type != "none" { if _, err := logger.GetLogDriver(config.LogConfig.Type); err != nil { return nil, fmt.Errorf("error finding the logging driver: %v", err) } } logrus.Debugf("Using default logging driver %s", config.LogConfig.Type) // Configure and validate the kernels security support if err := configureKernelSecuritySupport(config, d.driver.String()); err != nil { return nil, err } daemonRepo := filepath.Join(config.Root, "containers") if err := system.MkdirAll(daemonRepo, 0700); err != nil { return nil, err } // Migrate the container if it is aufs and aufs is enabled if err := migrateIfDownlevel(d.driver, config.Root); err != nil { return nil, err } logrus.Debug("Creating images graph") g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver) if err != nil { return nil, err } // Configure the volumes driver volStore, err := configureVolumes(config) if err != nil { return nil, err } trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath) if err != nil { return nil, err } trustDir := filepath.Join(config.Root, "trust") if err := system.MkdirAll(trustDir, 0700); err != nil { return nil, err } trustService, err := trust.NewStore(trustDir) if err != nil { return nil, fmt.Errorf("could not create trust store: %s", err) } eventsService := events.New() logrus.Debug("Creating repository list") tagCfg := &graph.TagStoreConfig{ Graph: g, Key: trustKey, Registry: registryService, Events: eventsService, Trust: trustService, } repositories, err := graph.NewTagStore(filepath.Join(config.Root, "repositories-"+d.driver.String()), tagCfg) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store repositories-%s: %s", d.driver.String(), err) } if restorer, ok := d.driver.(graphdriver.ImageRestorer); ok { if _, err := restorer.RestoreCustomImages(repositories, g); err != nil { return nil, fmt.Errorf("Couldn't restore custom images: %s", err) } } d.netController, err = initNetworkController(config) if err != nil { return nil, fmt.Errorf("Error initializing network controller: %v", err) } graphdbPath := filepath.Join(config.Root, "linkgraph.db") graph, err := graphdb.NewSqliteConn(graphdbPath) if err != nil { return nil, err } d.containerGraphDB = graph var sysInitPath string if config.ExecDriver == "lxc" { initPath, err := configureSysInit(config) if err != nil { return nil, err } sysInitPath = initPath } sysInfo := sysinfo.New(false) // Check if Devices cgroup is mounted, it is hard requirement for container security, // on Linux/FreeBSD. if runtime.GOOS != "windows" && !sysInfo.CgroupDevicesEnabled { return nil, fmt.Errorf("Devices cgroup isn't mounted") } ed, err := execdrivers.NewDriver(config.ExecDriver, config.ExecOptions, config.ExecRoot, config.Root, sysInitPath, sysInfo) if err != nil { return nil, err } // Discovery is only enabled when the daemon is launched with an address to advertise. When // initialized, the daemon is registered and we can store the discovery backend as its read-only // DiscoveryWatcher version. if config.ClusterStore != "" && config.ClusterAdvertise != "" { var err error if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise); err != nil { return nil, fmt.Errorf("discovery initialization failed (%v)", err) } } d.ID = trustKey.PublicKey().KeyID() d.repository = daemonRepo d.containers = &contStore{s: make(map[string]*Container)} d.execCommands = newExecStore() d.graph = g d.repositories = repositories d.idIndex = truncindex.NewTruncIndex([]string{}) d.configStore = config d.sysInitPath = sysInitPath d.execDriver = ed d.statsCollector = newStatsCollector(1 * time.Second) d.defaultLogConfig = config.LogConfig d.RegistryService = registryService d.EventsService = eventsService d.volumes = volStore d.root = config.Root if err := d.cleanupMounts(); err != nil { return nil, err } go d.execCommandGC() if err := d.restore(); err != nil { return nil, err } return d, nil }
func (daemon *Daemon) restore() error { type cr struct { container *Container registered bool } var ( debug = os.Getenv("DEBUG") != "" currentDriver = daemon.driver.String() containers = make(map[string]*cr) ) if !debug { logrus.Info("Loading containers: start.") } dir, err := ioutil.ReadDir(daemon.repository) if err != nil { return err } for _, v := range dir { id := v.Name() container, err := daemon.load(id) if !debug && logrus.GetLevel() == logrus.InfoLevel { fmt.Print(".") } if err != nil { logrus.Errorf("Failed to load container %v: %v", id, err) continue } // Ignore the container if it does not support the current driver being used by the graph if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver { logrus.Debugf("Loaded container %v", container.ID) containers[container.ID] = &cr{container: container} } else { logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) } } if entities := daemon.containerGraphDB.List("/", -1); entities != nil { for _, p := range entities.Paths() { if !debug && logrus.GetLevel() == logrus.InfoLevel { fmt.Print(".") } e := entities[p] if c, ok := containers[e.ID()]; ok { c.registered = true } } } group := sync.WaitGroup{} ctx := context.Background() for _, c := range containers { group.Add(1) go func(container *Container, registered bool) { defer group.Done() if !registered { // Try to set the default name for a container if it exists prior to links container.Name, err = daemon.generateNewName(container.ID) if err != nil { logrus.Debugf("Setting default id - %s", err) } } if err := daemon.Register(ctx, container); err != nil { logrus.Errorf("Failed to register container %s: %s", container.ID, err) // The container register failed should not be started. return } // check the restart policy on the containers and restart any container with // the restart policy of "always" if daemon.configStore.AutoRestart && container.shouldRestart() { logrus.Debugf("Starting container %s", container.ID) if err := container.Start(ctx); err != nil { logrus.Errorf("Failed to start container %s: %s", container.ID, err) } } }(c.container, c.registered) } group.Wait() if !debug { if logrus.GetLevel() == logrus.InfoLevel { fmt.Println() } logrus.Info("Loading containers: done.") } return nil }