1package provision
2
3import (
4	"fmt"
5	"regexp"
6	"strings"
7	"testing"
8
9	"github.com/docker/machine/drivers/fakedriver"
10	"github.com/docker/machine/libmachine/auth"
11	"github.com/docker/machine/libmachine/engine"
12	"github.com/docker/machine/libmachine/provision/pkgaction"
13	"github.com/docker/machine/libmachine/provision/provisiontest"
14	"github.com/docker/machine/libmachine/provision/serviceaction"
15	"github.com/docker/machine/libmachine/swarm"
16	"github.com/stretchr/testify/assert"
17)
18
19var (
20	reDaemonListening = ":2376\\s+.*:.*"
21)
22
23func TestMatchNetstatOutMissing(t *testing.T) {
24	nsOut := `Active Internet connections (servers and established)
25Proto Recv-Q Send-Q Local Address           Foreign Address         State
26tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
27tcp        0      0 0.0.0.0:237             0.0.0.0:*               LISTEN
28tcp6       0      0 :::22                   :::*                    LISTEN
29tcp6       0      0 :::23760                :::*                    LISTEN`
30	if matchNetstatOut(reDaemonListening, nsOut) {
31		t.Fatal("Expected not to match the netstat output as showing the daemon listening but got a match")
32	}
33}
34
35func TestMatchNetstatOutPresent(t *testing.T) {
36	nsOut := `Active Internet connections (servers and established)
37Proto Recv-Q Send-Q Local Address           Foreign Address         State
38tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
39tcp6       0      0 :::2376                 :::*                    LISTEN
40tcp6       0      0 :::22                   :::*                    LISTEN`
41	if !matchNetstatOut(reDaemonListening, nsOut) {
42		t.Fatal("Expected to match the netstat output as showing the daemon listening but didn't")
43	}
44}
45
46func TestMatchSsOutMissing(t *testing.T) {
47	ssOut := `State      Recv-Q Send-Q Local Address:Port               Peer Address:Port
48LISTEN     0      128          *:22                       *:*
49LISTEN     0      128         :::22                      :::*
50LISTEN     0      128         :::23760                   :::*                  `
51	if matchNetstatOut(reDaemonListening, ssOut) {
52		t.Fatal("Expected not to match the ss output as showing the daemon listening but got a match")
53	}
54}
55
56func TestMatchSsOutPresent(t *testing.T) {
57	ssOut := `State      Recv-Q Send-Q Local Address:Port               Peer Address:Port
58LISTEN     0      128          *:22                       *:*
59LISTEN     0      128         :::22                      :::*
60LISTEN     0      128         :::2376                    :::*                  `
61	if !matchNetstatOut(reDaemonListening, ssOut) {
62		t.Fatal("Expected to match the ss output as showing the daemon listening but didn't")
63	}
64}
65
66func TestGenerateDockerOptionsBoot2Docker(t *testing.T) {
67	p := &Boot2DockerProvisioner{
68		Driver: &fakedriver.Driver{},
69	}
70	dockerPort := 1234
71	p.AuthOptions = auth.Options{
72		CaCertRemotePath:     "/test/ca-cert",
73		ServerKeyRemotePath:  "/test/server-key",
74		ServerCertRemotePath: "/test/server-cert",
75	}
76	engineConfigPath := "/var/lib/boot2docker/profile"
77
78	dockerCfg, err := p.GenerateDockerOptions(dockerPort)
79	if err != nil {
80		t.Fatal(err)
81	}
82
83	if dockerCfg.EngineOptionsPath != engineConfigPath {
84		t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineOptionsPath)
85	}
86
87	if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)) == -1 {
88		t.Fatalf("-H docker port invalid; expected %d", dockerPort)
89	}
90
91	if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", p.AuthOptions.CaCertRemotePath)) == -1 {
92		t.Fatalf("CACERT option invalid; expected %s", p.AuthOptions.CaCertRemotePath)
93	}
94
95	if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", p.AuthOptions.ServerKeyRemotePath)) == -1 {
96		t.Fatalf("SERVERKEY option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath)
97	}
98
99	if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", p.AuthOptions.ServerCertRemotePath)) == -1 {
100		t.Fatalf("SERVERCERT option invalid; expected %s", p.AuthOptions.ServerCertRemotePath)
101	}
102}
103
104func TestMachinePortBoot2Docker(t *testing.T) {
105	p := &Boot2DockerProvisioner{
106		Driver: &fakedriver.Driver{},
107	}
108	dockerPort := engine.DefaultPort
109	bindURL := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
110	p.AuthOptions = auth.Options{
111		CaCertRemotePath:     "/test/ca-cert",
112		ServerKeyRemotePath:  "/test/server-key",
113		ServerCertRemotePath: "/test/server-cert",
114	}
115
116	cfg, err := p.GenerateDockerOptions(dockerPort)
117	if err != nil {
118		t.Fatal(err)
119	}
120	re := regexp.MustCompile("-H tcp://.*:(.+)")
121	m := re.FindStringSubmatch(cfg.EngineOptions)
122	if len(m) == 0 {
123		t.Errorf("could not find port %d in engine config", dockerPort)
124	}
125
126	b := m[0]
127	u := strings.Split(b, " ")
128	url := u[1]
129	url = strings.Replace(url, "'", "", -1)
130	url = strings.Replace(url, "\\\"", "", -1)
131	if url != bindURL {
132		t.Errorf("expected url %s; received %s", bindURL, url)
133	}
134}
135
136func TestMachineCustomPortBoot2Docker(t *testing.T) {
137	p := &Boot2DockerProvisioner{
138		Driver: &fakedriver.Driver{},
139	}
140	dockerPort := 3376
141	bindURL := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
142	p.AuthOptions = auth.Options{
143		CaCertRemotePath:     "/test/ca-cert",
144		ServerKeyRemotePath:  "/test/server-key",
145		ServerCertRemotePath: "/test/server-cert",
146	}
147
148	cfg, err := p.GenerateDockerOptions(dockerPort)
149	if err != nil {
150		t.Fatal(err)
151	}
152
153	re := regexp.MustCompile("-H tcp://.*:(.+)")
154	m := re.FindStringSubmatch(cfg.EngineOptions)
155	if len(m) == 0 {
156		t.Errorf("could not find port %d in engine config", dockerPort)
157	}
158
159	b := m[0]
160	u := strings.Split(b, " ")
161	url := u[1]
162	url = strings.Replace(url, "'", "", -1)
163	url = strings.Replace(url, "\\\"", "", -1)
164	if url != bindURL {
165		t.Errorf("expected url %s; received %s", bindURL, url)
166	}
167}
168
169func TestUbuntuSystemdDaemonBinary(t *testing.T) {
170	p := NewUbuntuSystemdProvisioner(&fakedriver.Driver{}).(*UbuntuSystemdProvisioner)
171	cases := []struct {
172		output, want string
173	}{
174		{"Docker version 1.9.1\n", "docker daemon"},
175		{"Docker version 1.11.2\n", "docker daemon"},
176		{"Docker version 1.12.0\n", "dockerd"},
177		{"Docker version 1.13.0\n", "dockerd"},
178	}
179
180	sshCmder := &provisiontest.FakeSSHCommander{
181		Responses: make(map[string]string),
182	}
183	p.SSHCommander = sshCmder
184
185	for _, tc := range cases {
186		sshCmder.Responses["docker --version"] = tc.output
187		opts, err := p.GenerateDockerOptions(1234)
188		if err != nil {
189			t.Fatal(err)
190		}
191		if !strings.Contains(opts.EngineOptions, tc.want) {
192			t.Fatal("incorrect docker daemon binary in engine options")
193		}
194	}
195}
196
197type fakeProvisioner struct {
198	GenericProvisioner
199}
200
201func (provisioner *fakeProvisioner) Package(name string, action pkgaction.PackageAction) error {
202	return nil
203}
204
205func (provisioner *fakeProvisioner) Provision(swarmOptions swarm.Options, authOptions auth.Options, engineOptions engine.Options) error {
206	return nil
207}
208
209func (provisioner *fakeProvisioner) Service(name string, action serviceaction.ServiceAction) error {
210	return nil
211}
212
213func (provisioner *fakeProvisioner) String() string {
214	return "fake"
215}
216
217func TestDecideStorageDriver(t *testing.T) {
218	var tests = []struct {
219		suppliedDriver       string
220		defaultDriver        string
221		remoteFilesystemType string
222		expectedDriver       string
223	}{
224		{"", "aufs", "ext4", "aufs"},
225		{"", "aufs", "btrfs", "btrfs"},
226		{"", "overlay", "btrfs", "overlay"},
227		{"devicemapper", "aufs", "ext4", "devicemapper"},
228		{"devicemapper", "aufs", "btrfs", "devicemapper"},
229	}
230
231	p := &fakeProvisioner{GenericProvisioner{
232		Driver: &fakedriver.Driver{},
233	}}
234	for _, test := range tests {
235		p.SSHCommander = provisiontest.NewFakeSSHCommander(
236			provisiontest.FakeSSHCommanderOptions{
237				FilesystemType: test.remoteFilesystemType,
238			},
239		)
240		storageDriver, err := decideStorageDriver(p, test.defaultDriver, test.suppliedDriver)
241		assert.NoError(t, err)
242		assert.Equal(t, test.expectedDriver, storageDriver)
243	}
244}
245
246func TestGetFilesystemType(t *testing.T) {
247	p := &fakeProvisioner{GenericProvisioner{
248		Driver: &fakedriver.Driver{},
249	}}
250	p.SSHCommander = &provisiontest.FakeSSHCommander{
251		Responses: map[string]string{
252			"stat -f -c %T /var/lib": "btrfs\n",
253		},
254	}
255	fsType, err := getFilesystemType(p, "/var/lib")
256	assert.NoError(t, err)
257	assert.Equal(t, "btrfs", fsType)
258}
259
260func TestDockerClientVersion(t *testing.T) {
261	cases := []struct {
262		output, want string
263	}{
264		{"Docker version 1.9.1, build a34a1d5\n", "1.9.1"},
265		{"Docker version 1.9.1\n", "1.9.1"},
266		{"Docker version 1.13.0-rc1, build deadbeef\n", "1.13.0-rc1"},
267		{"Docker version 1.13.0-dev, build deadbeef\n", "1.13.0-dev"},
268	}
269
270	sshCmder := &provisiontest.FakeSSHCommander{
271		Responses: make(map[string]string),
272	}
273
274	for _, tc := range cases {
275		sshCmder.Responses["docker --version"] = tc.output
276		got, err := DockerClientVersion(sshCmder)
277		if err != nil {
278			t.Fatal(err)
279		}
280		if got != tc.want {
281			t.Errorf("Unexpected version string from %q; got %q, want %q", tc.output, tc.want, got)
282		}
283	}
284}
285