docker_api_swarm_test.go 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. //go:build !windows
  2. package main
  3. import (
  4. "context"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "runtime"
  11. "strings"
  12. "sync"
  13. "testing"
  14. "time"
  15. "github.com/cloudflare/cfssl/csr"
  16. "github.com/cloudflare/cfssl/helpers"
  17. "github.com/cloudflare/cfssl/initca"
  18. "github.com/docker/docker/api/types"
  19. "github.com/docker/docker/api/types/container"
  20. "github.com/docker/docker/api/types/swarm"
  21. "github.com/docker/docker/errdefs"
  22. "github.com/docker/docker/integration-cli/checker"
  23. "github.com/docker/docker/integration-cli/daemon"
  24. "github.com/docker/docker/testutil"
  25. testdaemon "github.com/docker/docker/testutil/daemon"
  26. "github.com/docker/docker/testutil/request"
  27. "github.com/moby/swarmkit/v2/ca"
  28. "gotest.tools/v3/assert"
  29. is "gotest.tools/v3/assert/cmp"
  30. "gotest.tools/v3/poll"
  31. )
  32. var defaultReconciliationTimeout = 30 * time.Second
  33. func (s *DockerSwarmSuite) TestAPISwarmInit(c *testing.T) {
  34. ctx := testutil.GetContext(c)
  35. // todo: should find a better way to verify that components are running than /info
  36. d1 := s.AddDaemon(ctx, c, true, true)
  37. info := d1.SwarmInfo(ctx, c)
  38. assert.Equal(c, info.ControlAvailable, true)
  39. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  40. assert.Equal(c, info.Cluster.RootRotationInProgress, false)
  41. d2 := s.AddDaemon(ctx, c, true, false)
  42. info = d2.SwarmInfo(ctx, c)
  43. assert.Equal(c, info.ControlAvailable, false)
  44. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  45. // Leaving cluster
  46. assert.NilError(c, d2.SwarmLeave(ctx, c, false))
  47. info = d2.SwarmInfo(ctx, c)
  48. assert.Equal(c, info.ControlAvailable, false)
  49. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  50. d2.SwarmJoin(ctx, c, swarm.JoinRequest{
  51. ListenAddr: d1.SwarmListenAddr(),
  52. JoinToken: d1.JoinTokens(c).Worker,
  53. RemoteAddrs: []string{d1.SwarmListenAddr()},
  54. })
  55. info = d2.SwarmInfo(ctx, c)
  56. assert.Equal(c, info.ControlAvailable, false)
  57. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  58. // Current state restoring after restarts
  59. d1.Stop(c)
  60. d2.Stop(c)
  61. d1.StartNode(c)
  62. d2.StartNode(c)
  63. info = d1.SwarmInfo(ctx, c)
  64. assert.Equal(c, info.ControlAvailable, true)
  65. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  66. info = d2.SwarmInfo(ctx, c)
  67. assert.Equal(c, info.ControlAvailable, false)
  68. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  69. }
  70. func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *testing.T) {
  71. ctx := testutil.GetContext(c)
  72. d1 := s.AddDaemon(ctx, c, false, false)
  73. d1.SwarmInit(ctx, c, swarm.InitRequest{})
  74. // todo: error message differs depending if some components of token are valid
  75. d2 := s.AddDaemon(ctx, c, false, false)
  76. c2 := d2.NewClientT(c)
  77. err := c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  78. ListenAddr: d2.SwarmListenAddr(),
  79. RemoteAddrs: []string{d1.SwarmListenAddr()},
  80. })
  81. assert.ErrorContains(c, err, "join token is necessary")
  82. info := d2.SwarmInfo(ctx, c)
  83. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  84. err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  85. ListenAddr: d2.SwarmListenAddr(),
  86. JoinToken: "foobaz",
  87. RemoteAddrs: []string{d1.SwarmListenAddr()},
  88. })
  89. assert.ErrorContains(c, err, "invalid join token")
  90. info = d2.SwarmInfo(ctx, c)
  91. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  92. workerToken := d1.JoinTokens(c).Worker
  93. d2.SwarmJoin(ctx, c, swarm.JoinRequest{
  94. ListenAddr: d2.SwarmListenAddr(),
  95. JoinToken: workerToken,
  96. RemoteAddrs: []string{d1.SwarmListenAddr()},
  97. })
  98. info = d2.SwarmInfo(ctx, c)
  99. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  100. assert.NilError(c, d2.SwarmLeave(ctx, c, false))
  101. info = d2.SwarmInfo(ctx, c)
  102. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  103. // change tokens
  104. d1.RotateTokens(c)
  105. err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  106. ListenAddr: d2.SwarmListenAddr(),
  107. JoinToken: workerToken,
  108. RemoteAddrs: []string{d1.SwarmListenAddr()},
  109. })
  110. assert.ErrorContains(c, err, "join token is necessary")
  111. info = d2.SwarmInfo(ctx, c)
  112. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  113. workerToken = d1.JoinTokens(c).Worker
  114. d2.SwarmJoin(ctx, c, swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.SwarmListenAddr()}})
  115. info = d2.SwarmInfo(ctx, c)
  116. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  117. assert.NilError(c, d2.SwarmLeave(ctx, c, false))
  118. info = d2.SwarmInfo(ctx, c)
  119. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  120. // change spec, don't change tokens
  121. d1.UpdateSwarm(c, func(s *swarm.Spec) {})
  122. err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  123. ListenAddr: d2.SwarmListenAddr(),
  124. RemoteAddrs: []string{d1.SwarmListenAddr()},
  125. })
  126. assert.ErrorContains(c, err, "join token is necessary")
  127. info = d2.SwarmInfo(ctx, c)
  128. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  129. d2.SwarmJoin(ctx, c, swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.SwarmListenAddr()}})
  130. info = d2.SwarmInfo(ctx, c)
  131. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  132. assert.NilError(c, d2.SwarmLeave(ctx, c, false))
  133. info = d2.SwarmInfo(ctx, c)
  134. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  135. }
  136. func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *testing.T) {
  137. ctx := testutil.GetContext(c)
  138. d1 := s.AddDaemon(ctx, c, false, false)
  139. d1.SwarmInit(ctx, c, swarm.InitRequest{})
  140. d1.UpdateSwarm(c, func(s *swarm.Spec) {
  141. s.CAConfig.ExternalCAs = []*swarm.ExternalCA{
  142. {
  143. Protocol: swarm.ExternalCAProtocolCFSSL,
  144. URL: "https://thishasnoca.org",
  145. },
  146. {
  147. Protocol: swarm.ExternalCAProtocolCFSSL,
  148. URL: "https://thishasacacert.org",
  149. CACert: "cacert",
  150. },
  151. }
  152. })
  153. info := d1.SwarmInfo(ctx, c)
  154. assert.Equal(c, len(info.Cluster.Spec.CAConfig.ExternalCAs), 2)
  155. assert.Equal(c, info.Cluster.Spec.CAConfig.ExternalCAs[0].CACert, "")
  156. assert.Equal(c, info.Cluster.Spec.CAConfig.ExternalCAs[1].CACert, "cacert")
  157. }
  158. func (s *DockerSwarmSuite) TestAPISwarmCAHash(c *testing.T) {
  159. ctx := testutil.GetContext(c)
  160. d1 := s.AddDaemon(ctx, c, true, true)
  161. d2 := s.AddDaemon(ctx, c, false, false)
  162. splitToken := strings.Split(d1.JoinTokens(c).Worker, "-")
  163. splitToken[2] = "1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e"
  164. replacementToken := strings.Join(splitToken, "-")
  165. c2 := d2.NewClientT(c)
  166. err := c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  167. ListenAddr: d2.SwarmListenAddr(),
  168. JoinToken: replacementToken,
  169. RemoteAddrs: []string{d1.SwarmListenAddr()},
  170. })
  171. assert.ErrorContains(c, err, "remote CA does not match fingerprint")
  172. }
  173. func (s *DockerSwarmSuite) TestAPISwarmPromoteDemote(c *testing.T) {
  174. ctx := testutil.GetContext(c)
  175. d1 := s.AddDaemon(ctx, c, false, false)
  176. d1.SwarmInit(ctx, c, swarm.InitRequest{})
  177. d2 := s.AddDaemon(ctx, c, true, false)
  178. info := d2.SwarmInfo(ctx, c)
  179. assert.Equal(c, info.ControlAvailable, false)
  180. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  181. d1.UpdateNode(ctx, c, d2.NodeID(), func(n *swarm.Node) {
  182. n.Spec.Role = swarm.NodeRoleManager
  183. })
  184. poll.WaitOn(c, pollCheck(c, d2.CheckControlAvailable(ctx), checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  185. d1.UpdateNode(ctx, c, d2.NodeID(), func(n *swarm.Node) {
  186. n.Spec.Role = swarm.NodeRoleWorker
  187. })
  188. poll.WaitOn(c, pollCheck(c, d2.CheckControlAvailable(ctx), checker.False()), poll.WithTimeout(defaultReconciliationTimeout))
  189. // Wait for the role to change to worker in the cert. This is partially
  190. // done because it's something worth testing in its own right, and
  191. // partially because changing the role from manager to worker and then
  192. // back to manager quickly might cause the node to pause for awhile
  193. // while waiting for the role to change to worker, and the test can
  194. // time out during this interval.
  195. poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) {
  196. certBytes, err := os.ReadFile(filepath.Join(d2.Folder, "root", "swarm", "certificates", "swarm-node.crt"))
  197. if err != nil {
  198. return "", fmt.Sprintf("error: %v", err)
  199. }
  200. certs, err := helpers.ParseCertificatesPEM(certBytes)
  201. if err == nil && len(certs) > 0 && len(certs[0].Subject.OrganizationalUnit) > 0 {
  202. return certs[0].Subject.OrganizationalUnit[0], ""
  203. }
  204. return "", "could not get organizational unit from certificate"
  205. }, checker.Equals("swarm-worker")), poll.WithTimeout(defaultReconciliationTimeout))
  206. // Demoting last node should fail
  207. node := d1.GetNode(ctx, c, d1.NodeID())
  208. node.Spec.Role = swarm.NodeRoleWorker
  209. url := fmt.Sprintf("/nodes/%s/update?version=%d", node.ID, node.Version.Index)
  210. res, body, err := request.Post(testutil.GetContext(c), url, request.Host(d1.Sock()), request.JSONBody(node.Spec))
  211. assert.NilError(c, err)
  212. b, err := request.ReadBody(body)
  213. assert.NilError(c, err)
  214. assert.Equal(c, res.StatusCode, http.StatusBadRequest, "output: %q", string(b))
  215. // The warning specific to demoting the last manager is best-effort and
  216. // won't appear until the Role field of the demoted manager has been
  217. // updated.
  218. // Yes, I know this looks silly, but checker.Matches is broken, since
  219. // it anchors the regexp contrary to the documentation, and this makes
  220. // it impossible to match something that includes a line break.
  221. if !strings.Contains(string(b), "last manager of the swarm") {
  222. assert.Assert(c, strings.Contains(string(b), "this would result in a loss of quorum"))
  223. }
  224. info = d1.SwarmInfo(ctx, c)
  225. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  226. assert.Equal(c, info.ControlAvailable, true)
  227. // Promote already demoted node
  228. d1.UpdateNode(ctx, c, d2.NodeID(), func(n *swarm.Node) {
  229. n.Spec.Role = swarm.NodeRoleManager
  230. })
  231. poll.WaitOn(c, pollCheck(c, d2.CheckControlAvailable(ctx), checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  232. }
  233. func (s *DockerSwarmSuite) TestAPISwarmLeaderProxy(c *testing.T) {
  234. ctx := testutil.GetContext(c)
  235. // add three managers, one of these is leader
  236. d1 := s.AddDaemon(ctx, c, true, true)
  237. d2 := s.AddDaemon(ctx, c, true, true)
  238. d3 := s.AddDaemon(ctx, c, true, true)
  239. // start a service by hitting each of the 3 managers
  240. d1.CreateService(ctx, c, simpleTestService, func(s *swarm.Service) {
  241. s.Spec.Name = "test1"
  242. })
  243. d2.CreateService(ctx, c, simpleTestService, func(s *swarm.Service) {
  244. s.Spec.Name = "test2"
  245. })
  246. d3.CreateService(ctx, c, simpleTestService, func(s *swarm.Service) {
  247. s.Spec.Name = "test3"
  248. })
  249. // 3 services should be started now, because the requests were proxied to leader
  250. // query each node and make sure it returns 3 services
  251. for _, d := range []*daemon.Daemon{d1, d2, d3} {
  252. services := d.ListServices(ctx, c)
  253. assert.Equal(c, len(services), 3)
  254. }
  255. }
  256. func (s *DockerSwarmSuite) TestAPISwarmLeaderElection(c *testing.T) {
  257. ctx := testutil.GetContext(c)
  258. if runtime.GOARCH == "s390x" {
  259. c.Skip("Disabled on s390x")
  260. }
  261. if runtime.GOARCH == "ppc64le" {
  262. c.Skip("Disabled on ppc64le")
  263. }
  264. // Create 3 nodes
  265. d1 := s.AddDaemon(ctx, c, true, true)
  266. d2 := s.AddDaemon(ctx, c, true, true)
  267. d3 := s.AddDaemon(ctx, c, true, true)
  268. // assert that the first node we made is the leader, and the other two are followers
  269. assert.Equal(c, d1.GetNode(ctx, c, d1.NodeID()).ManagerStatus.Leader, true)
  270. assert.Equal(c, d1.GetNode(ctx, c, d2.NodeID()).ManagerStatus.Leader, false)
  271. assert.Equal(c, d1.GetNode(ctx, c, d3.NodeID()).ManagerStatus.Leader, false)
  272. d1.Stop(c)
  273. var (
  274. leader *daemon.Daemon // keep track of leader
  275. followers []*daemon.Daemon // keep track of followers
  276. )
  277. var lastErr error
  278. checkLeader := func(nodes ...*daemon.Daemon) checkF {
  279. return func(c *testing.T) (interface{}, string) {
  280. // clear these out before each run
  281. leader = nil
  282. followers = nil
  283. for _, d := range nodes {
  284. n := d.GetNode(ctx, c, d.NodeID(), func(err error) bool {
  285. if strings.Contains(err.Error(), context.DeadlineExceeded.Error()) || strings.Contains(err.Error(), "swarm does not have a leader") {
  286. lastErr = err
  287. return true
  288. }
  289. return false
  290. })
  291. if n == nil {
  292. return false, fmt.Sprintf("failed to get node: %v", lastErr)
  293. }
  294. if n.ManagerStatus.Leader {
  295. leader = d
  296. } else {
  297. followers = append(followers, d)
  298. }
  299. }
  300. if leader == nil {
  301. return false, "no leader elected"
  302. }
  303. return true, fmt.Sprintf("elected %v", leader.ID())
  304. }
  305. }
  306. // wait for an election to occur
  307. c.Logf("Waiting for election to occur...")
  308. poll.WaitOn(c, pollCheck(c, checkLeader(d2, d3), checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  309. // assert that we have a new leader
  310. assert.Assert(c, leader != nil)
  311. // Keep track of the current leader, since we want that to be chosen.
  312. stableleader := leader
  313. // add the d1, the initial leader, back
  314. d1.StartNode(c)
  315. // wait for possible election
  316. c.Logf("Waiting for possible election...")
  317. poll.WaitOn(c, pollCheck(c, checkLeader(d1, d2, d3), checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  318. // pick out the leader and the followers again
  319. // verify that we still only have 1 leader and 2 followers
  320. assert.Assert(c, leader != nil)
  321. assert.Equal(c, len(followers), 2)
  322. // and that after we added d1 back, the leader hasn't changed
  323. assert.Equal(c, leader.NodeID(), stableleader.NodeID())
  324. }
  325. func (s *DockerSwarmSuite) TestAPISwarmRaftQuorum(c *testing.T) {
  326. ctx := testutil.GetContext(c)
  327. if runtime.GOARCH == "s390x" {
  328. c.Skip("Disabled on s390x")
  329. }
  330. if runtime.GOARCH == "ppc64le" {
  331. c.Skip("Disabled on ppc64le")
  332. }
  333. d1 := s.AddDaemon(ctx, c, true, true)
  334. d2 := s.AddDaemon(ctx, c, true, true)
  335. d3 := s.AddDaemon(ctx, c, true, true)
  336. d1.CreateService(ctx, c, simpleTestService)
  337. d2.Stop(c)
  338. // make sure there is a leader
  339. poll.WaitOn(c, pollCheck(c, d1.CheckLeader(ctx), checker.IsNil()), poll.WithTimeout(defaultReconciliationTimeout))
  340. d1.CreateService(ctx, c, simpleTestService, func(s *swarm.Service) {
  341. s.Spec.Name = "top1"
  342. })
  343. d3.Stop(c)
  344. var service swarm.Service
  345. simpleTestService(&service)
  346. service.Spec.Name = "top2"
  347. cli := d1.NewClientT(c)
  348. defer cli.Close()
  349. // d1 will eventually step down from leader because there is no longer an active quorum, wait for that to happen
  350. poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) {
  351. _, err := cli.ServiceCreate(testutil.GetContext(c), service.Spec, types.ServiceCreateOptions{})
  352. return err.Error(), ""
  353. }, checker.Contains("Make sure more than half of the managers are online.")), poll.WithTimeout(defaultReconciliationTimeout*2))
  354. d2.StartNode(c)
  355. // make sure there is a leader
  356. poll.WaitOn(c, pollCheck(c, d1.CheckLeader(ctx), checker.IsNil()), poll.WithTimeout(defaultReconciliationTimeout))
  357. d1.CreateService(ctx, c, simpleTestService, func(s *swarm.Service) {
  358. s.Spec.Name = "top3"
  359. })
  360. }
  361. func (s *DockerSwarmSuite) TestAPISwarmLeaveRemovesContainer(c *testing.T) {
  362. ctx := testutil.GetContext(c)
  363. d := s.AddDaemon(ctx, c, true, true)
  364. instances := 2
  365. d.CreateService(ctx, c, simpleTestService, setInstances(instances))
  366. id, err := d.Cmd("run", "-d", "busybox", "top")
  367. assert.NilError(c, err, id)
  368. id = strings.TrimSpace(id)
  369. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances+1)), poll.WithTimeout(defaultReconciliationTimeout))
  370. assert.ErrorContains(c, d.SwarmLeave(ctx, c, false), "")
  371. assert.NilError(c, d.SwarmLeave(ctx, c, true))
  372. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(1)), poll.WithTimeout(defaultReconciliationTimeout))
  373. id2, err := d.Cmd("ps", "-q")
  374. assert.NilError(c, err, id2)
  375. assert.Assert(c, strings.HasPrefix(id, strings.TrimSpace(id2)))
  376. }
  377. // #23629
  378. func (s *DockerSwarmSuite) TestAPISwarmLeaveOnPendingJoin(c *testing.T) {
  379. testRequires(c, Network)
  380. ctx := testutil.GetContext(c)
  381. s.AddDaemon(ctx, c, true, true)
  382. d2 := s.AddDaemon(ctx, c, false, false)
  383. id, err := d2.Cmd("run", "-d", "busybox", "top")
  384. assert.NilError(c, err, id)
  385. id = strings.TrimSpace(id)
  386. c2 := d2.NewClientT(c)
  387. err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  388. ListenAddr: d2.SwarmListenAddr(),
  389. RemoteAddrs: []string{"123.123.123.123:1234"},
  390. })
  391. assert.ErrorContains(c, err, "Timeout was reached")
  392. info := d2.SwarmInfo(ctx, c)
  393. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStatePending)
  394. assert.NilError(c, d2.SwarmLeave(ctx, c, true))
  395. poll.WaitOn(c, pollCheck(c, d2.CheckActiveContainerCount(ctx), checker.Equals(1)), poll.WithTimeout(defaultReconciliationTimeout))
  396. id2, err := d2.Cmd("ps", "-q")
  397. assert.NilError(c, err, id2)
  398. assert.Assert(c, strings.HasPrefix(id, strings.TrimSpace(id2)))
  399. }
  400. // #23705
  401. func (s *DockerSwarmSuite) TestAPISwarmRestoreOnPendingJoin(c *testing.T) {
  402. testRequires(c, Network)
  403. ctx := testutil.GetContext(c)
  404. d := s.AddDaemon(ctx, c, false, false)
  405. client := d.NewClientT(c)
  406. err := client.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
  407. ListenAddr: d.SwarmListenAddr(),
  408. RemoteAddrs: []string{"123.123.123.123:1234"},
  409. })
  410. assert.ErrorContains(c, err, "Timeout was reached")
  411. poll.WaitOn(c, pollCheck(c, d.CheckLocalNodeState(ctx), checker.Equals(swarm.LocalNodeStatePending)), poll.WithTimeout(defaultReconciliationTimeout))
  412. d.RestartNode(c)
  413. info := d.SwarmInfo(ctx, c)
  414. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
  415. }
  416. func (s *DockerSwarmSuite) TestAPISwarmManagerRestore(c *testing.T) {
  417. ctx := testutil.GetContext(c)
  418. d1 := s.AddDaemon(ctx, c, true, true)
  419. instances := 2
  420. id := d1.CreateService(ctx, c, simpleTestService, setInstances(instances))
  421. d1.GetService(ctx, c, id)
  422. d1.RestartNode(c)
  423. d1.GetService(ctx, c, id)
  424. d2 := s.AddDaemon(ctx, c, true, true)
  425. d2.GetService(ctx, c, id)
  426. d2.RestartNode(c)
  427. d2.GetService(ctx, c, id)
  428. d3 := s.AddDaemon(ctx, c, true, true)
  429. d3.GetService(ctx, c, id)
  430. d3.RestartNode(c)
  431. d3.GetService(ctx, c, id)
  432. err := d3.Kill()
  433. assert.NilError(c, err)
  434. time.Sleep(1 * time.Second) // time to handle signal
  435. d3.StartNode(c)
  436. d3.GetService(ctx, c, id)
  437. }
  438. func (s *DockerSwarmSuite) TestAPISwarmScaleNoRollingUpdate(c *testing.T) {
  439. ctx := testutil.GetContext(c)
  440. d := s.AddDaemon(ctx, c, true, true)
  441. instances := 2
  442. id := d.CreateService(ctx, c, simpleTestService, setInstances(instances))
  443. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  444. containers := d.ActiveContainers(ctx, c)
  445. instances = 4
  446. d.UpdateService(ctx, c, d.GetService(ctx, c, id), setInstances(instances))
  447. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  448. containers2 := d.ActiveContainers(ctx, c)
  449. loop0:
  450. for _, c1 := range containers {
  451. for _, c2 := range containers2 {
  452. if c1 == c2 {
  453. continue loop0
  454. }
  455. }
  456. c.Errorf("container %v not found in new set %#v", c1, containers2)
  457. }
  458. }
  459. func (s *DockerSwarmSuite) TestAPISwarmInvalidAddress(c *testing.T) {
  460. ctx := testutil.GetContext(c)
  461. d := s.AddDaemon(ctx, c, false, false)
  462. req := swarm.InitRequest{
  463. ListenAddr: "",
  464. }
  465. res, _, err := request.Post(testutil.GetContext(c), "/swarm/init", request.Host(d.Sock()), request.JSONBody(req))
  466. assert.NilError(c, err)
  467. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  468. req2 := swarm.JoinRequest{
  469. ListenAddr: "0.0.0.0:2377",
  470. RemoteAddrs: []string{""},
  471. }
  472. res, _, err = request.Post(testutil.GetContext(c), "/swarm/join", request.Host(d.Sock()), request.JSONBody(req2))
  473. assert.NilError(c, err)
  474. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  475. }
  476. func (s *DockerSwarmSuite) TestAPISwarmForceNewCluster(c *testing.T) {
  477. ctx := testutil.GetContext(c)
  478. d1 := s.AddDaemon(ctx, c, true, true)
  479. d2 := s.AddDaemon(ctx, c, true, true)
  480. instances := 2
  481. id := d1.CreateService(ctx, c, simpleTestService, setInstances(instances))
  482. poll.WaitOn(c, pollCheck(c, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount(ctx), d2.CheckActiveContainerCount(ctx)), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  483. // drain d2, all containers should move to d1
  484. d1.UpdateNode(ctx, c, d2.NodeID(), func(n *swarm.Node) {
  485. n.Spec.Availability = swarm.NodeAvailabilityDrain
  486. })
  487. poll.WaitOn(c, pollCheck(c, d1.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  488. poll.WaitOn(c, pollCheck(c, d2.CheckActiveContainerCount(ctx), checker.Equals(0)), poll.WithTimeout(defaultReconciliationTimeout))
  489. d2.Stop(c)
  490. d1.SwarmInit(ctx, c, swarm.InitRequest{
  491. ForceNewCluster: true,
  492. Spec: swarm.Spec{},
  493. })
  494. poll.WaitOn(c, pollCheck(c, d1.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  495. d3 := s.AddDaemon(ctx, c, true, true)
  496. info := d3.SwarmInfo(ctx, c)
  497. assert.Equal(c, info.ControlAvailable, true)
  498. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  499. instances = 4
  500. d3.UpdateService(ctx, c, d3.GetService(ctx, c, id), setInstances(instances))
  501. poll.WaitOn(c, pollCheck(c, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount(ctx), d3.CheckActiveContainerCount(ctx)), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  502. }
  503. func simpleTestService(s *swarm.Service) {
  504. ureplicas := uint64(1)
  505. restartDelay := 100 * time.Millisecond
  506. s.Spec = swarm.ServiceSpec{
  507. TaskTemplate: swarm.TaskSpec{
  508. ContainerSpec: &swarm.ContainerSpec{
  509. Image: "busybox:latest",
  510. Command: []string{"/bin/top"},
  511. },
  512. RestartPolicy: &swarm.RestartPolicy{
  513. Delay: &restartDelay,
  514. },
  515. },
  516. Mode: swarm.ServiceMode{
  517. Replicated: &swarm.ReplicatedService{
  518. Replicas: &ureplicas,
  519. },
  520. },
  521. }
  522. s.Spec.Name = "top"
  523. }
  524. func serviceForUpdate(s *swarm.Service) {
  525. ureplicas := uint64(1)
  526. restartDelay := 100 * time.Millisecond
  527. s.Spec = swarm.ServiceSpec{
  528. TaskTemplate: swarm.TaskSpec{
  529. ContainerSpec: &swarm.ContainerSpec{
  530. Image: "busybox:latest",
  531. Command: []string{"/bin/top"},
  532. },
  533. RestartPolicy: &swarm.RestartPolicy{
  534. Delay: &restartDelay,
  535. },
  536. },
  537. Mode: swarm.ServiceMode{
  538. Replicated: &swarm.ReplicatedService{
  539. Replicas: &ureplicas,
  540. },
  541. },
  542. UpdateConfig: &swarm.UpdateConfig{
  543. Parallelism: 2,
  544. Delay: 4 * time.Second,
  545. FailureAction: swarm.UpdateFailureActionContinue,
  546. },
  547. RollbackConfig: &swarm.UpdateConfig{
  548. Parallelism: 3,
  549. Delay: 4 * time.Second,
  550. FailureAction: swarm.UpdateFailureActionContinue,
  551. },
  552. }
  553. s.Spec.Name = "updatetest"
  554. }
  555. func setInstances(replicas int) testdaemon.ServiceConstructor {
  556. ureplicas := uint64(replicas)
  557. return func(s *swarm.Service) {
  558. s.Spec.Mode = swarm.ServiceMode{
  559. Replicated: &swarm.ReplicatedService{
  560. Replicas: &ureplicas,
  561. },
  562. }
  563. }
  564. }
  565. func setUpdateOrder(order string) testdaemon.ServiceConstructor {
  566. return func(s *swarm.Service) {
  567. if s.Spec.UpdateConfig == nil {
  568. s.Spec.UpdateConfig = &swarm.UpdateConfig{}
  569. }
  570. s.Spec.UpdateConfig.Order = order
  571. }
  572. }
  573. func setRollbackOrder(order string) testdaemon.ServiceConstructor {
  574. return func(s *swarm.Service) {
  575. if s.Spec.RollbackConfig == nil {
  576. s.Spec.RollbackConfig = &swarm.UpdateConfig{}
  577. }
  578. s.Spec.RollbackConfig.Order = order
  579. }
  580. }
  581. func setImage(image string) testdaemon.ServiceConstructor {
  582. return func(s *swarm.Service) {
  583. if s.Spec.TaskTemplate.ContainerSpec == nil {
  584. s.Spec.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
  585. }
  586. s.Spec.TaskTemplate.ContainerSpec.Image = image
  587. }
  588. }
  589. func setFailureAction(failureAction string) testdaemon.ServiceConstructor {
  590. return func(s *swarm.Service) {
  591. s.Spec.UpdateConfig.FailureAction = failureAction
  592. }
  593. }
  594. func setMaxFailureRatio(maxFailureRatio float32) testdaemon.ServiceConstructor {
  595. return func(s *swarm.Service) {
  596. s.Spec.UpdateConfig.MaxFailureRatio = maxFailureRatio
  597. }
  598. }
  599. func setParallelism(parallelism uint64) testdaemon.ServiceConstructor {
  600. return func(s *swarm.Service) {
  601. s.Spec.UpdateConfig.Parallelism = parallelism
  602. }
  603. }
  604. func setConstraints(constraints []string) testdaemon.ServiceConstructor {
  605. return func(s *swarm.Service) {
  606. if s.Spec.TaskTemplate.Placement == nil {
  607. s.Spec.TaskTemplate.Placement = &swarm.Placement{}
  608. }
  609. s.Spec.TaskTemplate.Placement.Constraints = constraints
  610. }
  611. }
  612. func setPlacementPrefs(prefs []swarm.PlacementPreference) testdaemon.ServiceConstructor {
  613. return func(s *swarm.Service) {
  614. if s.Spec.TaskTemplate.Placement == nil {
  615. s.Spec.TaskTemplate.Placement = &swarm.Placement{}
  616. }
  617. s.Spec.TaskTemplate.Placement.Preferences = prefs
  618. }
  619. }
  620. func setGlobalMode(s *swarm.Service) {
  621. s.Spec.Mode = swarm.ServiceMode{
  622. Global: &swarm.GlobalService{},
  623. }
  624. }
  625. func checkClusterHealth(c *testing.T, cl []*daemon.Daemon, managerCount, workerCount int) {
  626. var totalMCount, totalWCount int
  627. ctx := testutil.GetContext(c)
  628. for _, d := range cl {
  629. var info swarm.Info
  630. // check info in a poll.WaitOn(), because if the cluster doesn't have a leader, `info` will return an error
  631. checkInfo := func(c *testing.T) (interface{}, string) {
  632. client := d.NewClientT(c)
  633. daemonInfo, err := client.Info(ctx)
  634. info = daemonInfo.Swarm
  635. return err, "cluster not ready in time"
  636. }
  637. poll.WaitOn(c, pollCheck(c, checkInfo, checker.IsNil()), poll.WithTimeout(defaultReconciliationTimeout))
  638. if !info.ControlAvailable {
  639. totalWCount++
  640. continue
  641. }
  642. var leaderFound bool
  643. totalMCount++
  644. var mCount, wCount int
  645. for _, n := range d.ListNodes(ctx, c) {
  646. waitReady := func(c *testing.T) (interface{}, string) {
  647. if n.Status.State == swarm.NodeStateReady {
  648. return true, ""
  649. }
  650. nn := d.GetNode(ctx, c, n.ID)
  651. n = *nn
  652. return n.Status.State == swarm.NodeStateReady, fmt.Sprintf("state of node %s, reported by %s", n.ID, d.NodeID())
  653. }
  654. poll.WaitOn(c, pollCheck(c, waitReady, checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  655. waitActive := func(c *testing.T) (interface{}, string) {
  656. if n.Spec.Availability == swarm.NodeAvailabilityActive {
  657. return true, ""
  658. }
  659. nn := d.GetNode(ctx, c, n.ID)
  660. n = *nn
  661. return n.Spec.Availability == swarm.NodeAvailabilityActive, fmt.Sprintf("availability of node %s, reported by %s", n.ID, d.NodeID())
  662. }
  663. poll.WaitOn(c, pollCheck(c, waitActive, checker.True()), poll.WithTimeout(defaultReconciliationTimeout))
  664. if n.Spec.Role == swarm.NodeRoleManager {
  665. assert.Assert(c, n.ManagerStatus != nil, "manager status of node %s (manager), reported by %s", n.ID, d.NodeID())
  666. if n.ManagerStatus.Leader {
  667. leaderFound = true
  668. }
  669. mCount++
  670. } else {
  671. assert.Assert(c, n.ManagerStatus == nil, "manager status of node %s (worker), reported by %s", n.ID, d.NodeID())
  672. wCount++
  673. }
  674. }
  675. assert.Equal(c, leaderFound, true, "lack of leader reported by node %s", info.NodeID)
  676. assert.Equal(c, mCount, managerCount, "managers count reported by node %s", info.NodeID)
  677. assert.Equal(c, wCount, workerCount, "workers count reported by node %s", info.NodeID)
  678. }
  679. assert.Equal(c, totalMCount, managerCount)
  680. assert.Equal(c, totalWCount, workerCount)
  681. }
  682. func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *testing.T) {
  683. ctx := testutil.GetContext(c)
  684. mCount, wCount := 5, 1
  685. var nodes []*daemon.Daemon
  686. for i := 0; i < mCount; i++ {
  687. manager := s.AddDaemon(ctx, c, true, true)
  688. info := manager.SwarmInfo(ctx, c)
  689. assert.Equal(c, info.ControlAvailable, true)
  690. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  691. nodes = append(nodes, manager)
  692. }
  693. for i := 0; i < wCount; i++ {
  694. worker := s.AddDaemon(ctx, c, true, false)
  695. info := worker.SwarmInfo(ctx, c)
  696. assert.Equal(c, info.ControlAvailable, false)
  697. assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateActive)
  698. nodes = append(nodes, worker)
  699. }
  700. // stop whole cluster
  701. {
  702. var wg sync.WaitGroup
  703. wg.Add(len(nodes))
  704. errs := make(chan error, len(nodes))
  705. for _, d := range nodes {
  706. go func(daemon *daemon.Daemon) {
  707. defer wg.Done()
  708. if err := daemon.StopWithError(); err != nil {
  709. errs <- err
  710. }
  711. }(d)
  712. }
  713. wg.Wait()
  714. close(errs)
  715. for err := range errs {
  716. assert.NilError(c, err)
  717. }
  718. }
  719. // start whole cluster
  720. {
  721. var wg sync.WaitGroup
  722. wg.Add(len(nodes))
  723. errs := make(chan error, len(nodes))
  724. for _, d := range nodes {
  725. go func(daemon *daemon.Daemon) {
  726. defer wg.Done()
  727. if err := daemon.StartWithError("--iptables=false"); err != nil {
  728. errs <- err
  729. }
  730. }(d)
  731. }
  732. wg.Wait()
  733. close(errs)
  734. for err := range errs {
  735. assert.NilError(c, err)
  736. }
  737. }
  738. checkClusterHealth(c, nodes, mCount, wCount)
  739. }
  740. func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateWithName(c *testing.T) {
  741. ctx := testutil.GetContext(c)
  742. d := s.AddDaemon(ctx, c, true, true)
  743. instances := 2
  744. id := d.CreateService(ctx, c, simpleTestService, setInstances(instances))
  745. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  746. service := d.GetService(ctx, c, id)
  747. instances = 5
  748. setInstances(instances)(service)
  749. cli := d.NewClientT(c)
  750. defer cli.Close()
  751. _, err := cli.ServiceUpdate(ctx, service.Spec.Name, service.Version, service.Spec, types.ServiceUpdateOptions{})
  752. assert.NilError(c, err)
  753. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  754. }
  755. // Unlocking an unlocked swarm results in an error
  756. func (s *DockerSwarmSuite) TestAPISwarmUnlockNotLocked(c *testing.T) {
  757. ctx := testutil.GetContext(c)
  758. d := s.AddDaemon(ctx, c, true, true)
  759. err := d.SwarmUnlock(c, swarm.UnlockRequest{UnlockKey: "wrong-key"})
  760. assert.ErrorContains(c, err, "swarm is not locked")
  761. }
  762. // #29885
  763. func (s *DockerSwarmSuite) TestAPISwarmErrorHandling(c *testing.T) {
  764. ctx := testutil.GetContext(c)
  765. ln, err := net.Listen("tcp", fmt.Sprintf(":%d", defaultSwarmPort))
  766. assert.NilError(c, err)
  767. defer ln.Close()
  768. d := s.AddDaemon(ctx, c, false, false)
  769. client := d.NewClientT(c)
  770. _, err = client.SwarmInit(testutil.GetContext(c), swarm.InitRequest{
  771. ListenAddr: d.SwarmListenAddr(),
  772. })
  773. assert.ErrorContains(c, err, "address already in use")
  774. }
  775. // Test case for 30178
  776. func (s *DockerSwarmSuite) TestAPISwarmHealthcheckNone(c *testing.T) {
  777. // Issue #36386 can be a independent one, which is worth further investigation.
  778. c.Skip("Root cause of Issue #36386 is needed")
  779. ctx := testutil.GetContext(c)
  780. d := s.AddDaemon(ctx, c, true, true)
  781. out, err := d.Cmd("network", "create", "-d", "overlay", "lb")
  782. assert.NilError(c, err, out)
  783. instances := 1
  784. d.CreateService(ctx, c, simpleTestService, setInstances(instances), func(s *swarm.Service) {
  785. if s.Spec.TaskTemplate.ContainerSpec == nil {
  786. s.Spec.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
  787. }
  788. s.Spec.TaskTemplate.ContainerSpec.Healthcheck = &container.HealthConfig{}
  789. s.Spec.TaskTemplate.Networks = []swarm.NetworkAttachmentConfig{
  790. {Target: "lb"},
  791. }
  792. })
  793. poll.WaitOn(c, pollCheck(c, d.CheckActiveContainerCount(ctx), checker.Equals(instances)), poll.WithTimeout(defaultReconciliationTimeout))
  794. containers := d.ActiveContainers(testutil.GetContext(c), c)
  795. out, err = d.Cmd("exec", containers[0], "ping", "-c1", "-W3", "top")
  796. assert.NilError(c, err, out)
  797. }
  798. func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *testing.T) {
  799. ctx := testutil.GetContext(c)
  800. m := s.AddDaemon(ctx, c, true, true)
  801. w := s.AddDaemon(ctx, c, true, false)
  802. info := m.SwarmInfo(ctx, c)
  803. currentTrustRoot := info.Cluster.TLSInfo.TrustRoot
  804. // rotate multiple times
  805. for i := 0; i < 4; i++ {
  806. var err error
  807. var cert, key []byte
  808. if i%2 != 0 {
  809. cert, _, key, err = initca.New(&csr.CertificateRequest{
  810. CN: "newRoot",
  811. KeyRequest: csr.NewKeyRequest(),
  812. CA: &csr.CAConfig{Expiry: ca.RootCAExpiration},
  813. })
  814. assert.NilError(c, err)
  815. }
  816. expectedCert := string(cert)
  817. m.UpdateSwarm(c, func(s *swarm.Spec) {
  818. s.CAConfig.SigningCACert = expectedCert
  819. s.CAConfig.SigningCAKey = string(key)
  820. s.CAConfig.ForceRotate++
  821. })
  822. // poll to make sure update succeeds
  823. var clusterTLSInfo swarm.TLSInfo
  824. for j := 0; j < 18; j++ {
  825. info := m.SwarmInfo(ctx, c)
  826. // the desired CA cert and key is always redacted
  827. assert.Equal(c, info.Cluster.Spec.CAConfig.SigningCAKey, "")
  828. assert.Equal(c, info.Cluster.Spec.CAConfig.SigningCACert, "")
  829. clusterTLSInfo = info.Cluster.TLSInfo
  830. // if root rotation is done and the trust root has changed, we don't have to poll anymore
  831. if !info.Cluster.RootRotationInProgress && clusterTLSInfo.TrustRoot != currentTrustRoot {
  832. break
  833. }
  834. // root rotation not done
  835. time.Sleep(250 * time.Millisecond)
  836. }
  837. if cert != nil {
  838. assert.Equal(c, clusterTLSInfo.TrustRoot, expectedCert)
  839. }
  840. // could take another second or two for the nodes to trust the new roots after they've all gotten
  841. // new TLS certificates
  842. for j := 0; j < 18; j++ {
  843. mInfo := m.GetNode(ctx, c, m.NodeID()).Description.TLSInfo
  844. wInfo := m.GetNode(ctx, c, w.NodeID()).Description.TLSInfo
  845. if mInfo.TrustRoot == clusterTLSInfo.TrustRoot && wInfo.TrustRoot == clusterTLSInfo.TrustRoot {
  846. break
  847. }
  848. // nodes don't trust root certs yet
  849. time.Sleep(250 * time.Millisecond)
  850. }
  851. assert.DeepEqual(c, m.GetNode(ctx, c, m.NodeID()).Description.TLSInfo, clusterTLSInfo)
  852. assert.DeepEqual(c, m.GetNode(ctx, c, w.NodeID()).Description.TLSInfo, clusterTLSInfo)
  853. currentTrustRoot = clusterTLSInfo.TrustRoot
  854. }
  855. }
  856. func (s *DockerSwarmSuite) TestAPINetworkInspectWithScope(c *testing.T) {
  857. ctx := testutil.GetContext(c)
  858. d := s.AddDaemon(ctx, c, true, true)
  859. name := "test-scoped-network"
  860. apiclient := d.NewClientT(c)
  861. resp, err := apiclient.NetworkCreate(ctx, name, types.NetworkCreate{Driver: "overlay"})
  862. assert.NilError(c, err)
  863. network, err := apiclient.NetworkInspect(ctx, name, types.NetworkInspectOptions{})
  864. assert.NilError(c, err)
  865. assert.Check(c, is.Equal("swarm", network.Scope))
  866. assert.Check(c, is.Equal(resp.ID, network.ID))
  867. _, err = apiclient.NetworkInspect(ctx, name, types.NetworkInspectOptions{Scope: "local"})
  868. assert.Check(c, is.ErrorType(err, errdefs.IsNotFound))
  869. }