diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index bbf5383c14..8dcea03b1a 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -27,8 +27,10 @@ import ( "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" blkiodev "github.com/opencontainers/runc/libcontainer/configs" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/mgr" ) const ( @@ -238,6 +240,29 @@ func checkSystem() error { return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed") } + // Ensure that the required Host Network Service and vmcompute services + // are running. Docker will fail in unexpected ways if this is not present. + var requiredServices = []string{"hns", "vmcompute"} + if err := ensureServicesInstalled(requiredServices); err != nil { + return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed") + } + + return nil +} + +func ensureServicesInstalled(services []string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + for _, service := range services { + s, err := m.OpenService(service) + if err != nil { + return errors.Wrapf(err, "failed to open service %s", service) + } + s.Close() + } return nil } diff --git a/daemon/daemon_windows_test.go b/daemon/daemon_windows_test.go new file mode 100644 index 0000000000..8b350cf20f --- /dev/null +++ b/daemon/daemon_windows_test.go @@ -0,0 +1,72 @@ +// +build windows + +package daemon + +import ( + "strings" + "testing" + + "golang.org/x/sys/windows/svc/mgr" +) + +const existingService = "Power" + +func TestEnsureServicesExist(t *testing.T) { + m, err := mgr.Connect() + if err != nil { + t.Fatal("failed to connect to service manager, this test needs admin") + } + defer m.Disconnect() + s, err := m.OpenService(existingService) + if err != nil { + t.Fatalf("expected to find known inbox service %q, this test needs a known inbox service to run correctly", existingService) + } + defer s.Close() + + input := []string{existingService} + err = ensureServicesInstalled(input) + if err != nil { + t.Fatalf("unexpected error for input %q: %q", input, err) + } +} + +func TestEnsureServicesExistErrors(t *testing.T) { + m, err := mgr.Connect() + if err != nil { + t.Fatal("failed to connect to service manager, this test needs admin") + } + defer m.Disconnect() + s, err := m.OpenService(existingService) + if err != nil { + t.Fatalf("expected to find known inbox service %q, this test needs a known inbox service to run correctly", existingService) + } + defer s.Close() + + for _, testcase := range []struct { + input []string + expectedError string + }{ + { + input: []string{"daemon_windows_test_fakeservice"}, + expectedError: "failed to open service daemon_windows_test_fakeservice", + }, + { + input: []string{"daemon_windows_test_fakeservice1", "daemon_windows_test_fakeservice2"}, + expectedError: "failed to open service daemon_windows_test_fakeservice1", + }, + { + input: []string{existingService, "daemon_windows_test_fakeservice"}, + expectedError: "failed to open service daemon_windows_test_fakeservice", + }, + } { + t.Run(strings.Join(testcase.input, ";"), func(t *testing.T) { + err := ensureServicesInstalled(testcase.input) + if err == nil { + t.Fatalf("expected error for input %v", testcase.input) + } + if !strings.Contains(err.Error(), testcase.expectedError) { + t.Fatalf("expected error %q to contain %q", err.Error(), testcase.expectedError) + } + }) + } +}