convert_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package convert
  14. import (
  15. "context"
  16. "os"
  17. "testing"
  18. "time"
  19. "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
  20. "github.com/Azure/go-autorest/autorest/to"
  21. "github.com/compose-spec/compose-go/types"
  22. "gotest.tools/v3/assert"
  23. is "gotest.tools/v3/assert/cmp"
  24. "github.com/docker/compose-cli/api/compose"
  25. "github.com/docker/compose-cli/api/containers"
  26. "github.com/docker/compose-cli/context/store"
  27. )
  28. var (
  29. convertCtx = store.AciContext{
  30. SubscriptionID: "subID",
  31. ResourceGroup: "rg",
  32. Location: "eu",
  33. }
  34. mockStorageHelper = &mockStorageLogin{}
  35. )
  36. func TestProjectName(t *testing.T) {
  37. project := types.Project{
  38. Name: "TEST",
  39. }
  40. containerGroup, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  41. assert.NilError(t, err)
  42. assert.Equal(t, *containerGroup.Name, "test")
  43. }
  44. func TestContainerGroupToContainer(t *testing.T) {
  45. myContainerGroup := containerinstance.ContainerGroup{
  46. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  47. IPAddress: &containerinstance.IPAddress{
  48. Ports: &[]containerinstance.Port{{
  49. Port: to.Int32Ptr(80),
  50. }},
  51. IP: to.StringPtr("42.42.42.42"),
  52. DNSNameLabel: to.StringPtr("myapp"),
  53. },
  54. OsType: "Linux",
  55. },
  56. }
  57. myContainer := containerinstance.Container{
  58. Name: to.StringPtr("myContainerID"),
  59. ContainerProperties: &containerinstance.ContainerProperties{
  60. Image: to.StringPtr("sha256:666"),
  61. Command: to.StringSlicePtr([]string{"mycommand"}),
  62. Ports: &[]containerinstance.ContainerPort{{
  63. Port: to.Int32Ptr(80),
  64. }},
  65. EnvironmentVariables: nil,
  66. InstanceView: &containerinstance.ContainerPropertiesInstanceView{
  67. RestartCount: nil,
  68. CurrentState: &containerinstance.ContainerState{
  69. State: to.StringPtr("Running"),
  70. },
  71. },
  72. Resources: &containerinstance.ResourceRequirements{
  73. Limits: &containerinstance.ResourceLimits{
  74. CPU: to.Float64Ptr(3),
  75. MemoryInGB: to.Float64Ptr(0.2),
  76. },
  77. Requests: &containerinstance.ResourceRequests{
  78. CPU: to.Float64Ptr(2),
  79. MemoryInGB: to.Float64Ptr(0.1),
  80. },
  81. },
  82. LivenessProbe: &containerinstance.ContainerProbe{
  83. Exec: &containerinstance.ContainerExec{
  84. Command: to.StringSlicePtr([]string{
  85. "my",
  86. "command",
  87. "--option",
  88. }),
  89. },
  90. PeriodSeconds: to.Int32Ptr(10),
  91. FailureThreshold: to.Int32Ptr(3),
  92. InitialDelaySeconds: to.Int32Ptr(2),
  93. TimeoutSeconds: to.Int32Ptr(1),
  94. },
  95. },
  96. }
  97. var expectedContainer = containers.Container{
  98. ID: "myContainerID",
  99. Status: "Running",
  100. Image: "sha256:666",
  101. Command: "mycommand",
  102. Platform: "Linux",
  103. Ports: []containers.Port{{
  104. HostPort: uint32(80),
  105. ContainerPort: uint32(80),
  106. Protocol: "tcp",
  107. HostIP: "42.42.42.42",
  108. }},
  109. Config: &containers.RuntimeConfig{
  110. FQDN: "myapp.eastus.azurecontainer.io",
  111. },
  112. HostConfig: &containers.HostConfig{
  113. CPULimit: 3,
  114. CPUReservation: 2,
  115. MemoryLimit: gbToBytes(0.2),
  116. MemoryReservation: gbToBytes(0.1),
  117. RestartPolicy: "any",
  118. },
  119. Healthcheck: containers.Healthcheck{
  120. Disable: false,
  121. Test: []string{
  122. "my",
  123. "command",
  124. "--option",
  125. },
  126. Interval: types.Duration(10 * time.Second),
  127. Retries: 3,
  128. StartPeriod: types.Duration(2 * time.Second),
  129. Timeout: types.Duration(time.Second),
  130. },
  131. }
  132. container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer, "eastus")
  133. assert.DeepEqual(t, container, expectedContainer)
  134. }
  135. func TestHealthcheckTranslation(t *testing.T) {
  136. test := []string{
  137. "my",
  138. "command",
  139. "--option",
  140. }
  141. interval := types.Duration(10 * time.Second)
  142. retries := uint64(42)
  143. startPeriod := types.Duration(2 * time.Second)
  144. timeout := types.Duration(3 * time.Second)
  145. project := types.Project{
  146. Services: []types.ServiceConfig{
  147. {
  148. Name: "service1",
  149. Image: "image1",
  150. HealthCheck: &types.HealthCheckConfig{
  151. Test: test,
  152. Timeout: &timeout,
  153. Interval: &interval,
  154. Retries: &retries,
  155. StartPeriod: &startPeriod,
  156. Disable: false,
  157. },
  158. },
  159. },
  160. }
  161. testHealthcheckTestPrefixRemoval := func(test []string, shellPreffix ...string) {
  162. project.Services[0].HealthCheck.Test = append(shellPreffix, test...)
  163. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  164. assert.NilError(t, err)
  165. assert.DeepEqual(t, (*group.Containers)[0].LivenessProbe.Exec.Command, to.StringSlicePtr(test))
  166. assert.Equal(t, *(*group.Containers)[0].LivenessProbe.PeriodSeconds, int32(10))
  167. assert.Assert(t, (*group.Containers)[0].LivenessProbe.SuccessThreshold == nil)
  168. assert.Equal(t, *(*group.Containers)[0].LivenessProbe.FailureThreshold, int32(42))
  169. assert.Equal(t, *(*group.Containers)[0].LivenessProbe.InitialDelaySeconds, int32(2))
  170. assert.Equal(t, *(*group.Containers)[0].LivenessProbe.TimeoutSeconds, int32(3))
  171. }
  172. testHealthcheckTestPrefixRemoval(test)
  173. testHealthcheckTestPrefixRemoval(test, "NONE")
  174. testHealthcheckTestPrefixRemoval(test, "CMD")
  175. testHealthcheckTestPrefixRemoval(test, "CMD-SHELL")
  176. project.Services[0].HealthCheck.Disable = true
  177. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  178. assert.NilError(t, err)
  179. assert.Assert(t, (*group.Containers)[0].LivenessProbe == nil)
  180. }
  181. func TestHealthcheckTranslationZeroValues(t *testing.T) {
  182. test := []string{
  183. "my",
  184. "command",
  185. "--option",
  186. }
  187. interval := types.Duration(0)
  188. retries := uint64(0)
  189. startPeriod := types.Duration(0)
  190. timeout := types.Duration(0)
  191. project := types.Project{
  192. Services: []types.ServiceConfig{
  193. {
  194. Name: "service1",
  195. Image: "image1",
  196. HealthCheck: &types.HealthCheckConfig{
  197. Test: test,
  198. Timeout: &timeout,
  199. Interval: &interval,
  200. Retries: &retries,
  201. StartPeriod: &startPeriod,
  202. Disable: false,
  203. },
  204. },
  205. },
  206. }
  207. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  208. assert.NilError(t, err)
  209. assert.DeepEqual(t, (*group.Containers)[0].LivenessProbe.Exec.Command, to.StringSlicePtr(test))
  210. assert.Assert(t, (*group.Containers)[0].LivenessProbe.PeriodSeconds == nil)
  211. assert.Assert(t, (*group.Containers)[0].LivenessProbe.SuccessThreshold == nil)
  212. assert.Assert(t, (*group.Containers)[0].LivenessProbe.FailureThreshold == nil)
  213. assert.Assert(t, (*group.Containers)[0].LivenessProbe.InitialDelaySeconds == nil)
  214. assert.Assert(t, (*group.Containers)[0].LivenessProbe.TimeoutSeconds == nil)
  215. }
  216. func TestContainerGroupToServiceStatus(t *testing.T) {
  217. myContainerGroup := containerinstance.ContainerGroup{
  218. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  219. IPAddress: &containerinstance.IPAddress{
  220. Ports: &[]containerinstance.Port{{
  221. Port: to.Int32Ptr(80),
  222. }},
  223. IP: to.StringPtr("42.42.42.42"),
  224. },
  225. },
  226. }
  227. myContainer := containerinstance.Container{
  228. Name: to.StringPtr("myContainerID"),
  229. ContainerProperties: &containerinstance.ContainerProperties{
  230. Ports: &[]containerinstance.ContainerPort{{
  231. Port: to.Int32Ptr(80),
  232. }},
  233. InstanceView: &containerinstance.ContainerPropertiesInstanceView{
  234. RestartCount: nil,
  235. CurrentState: &containerinstance.ContainerState{
  236. State: to.StringPtr("Running"),
  237. },
  238. },
  239. },
  240. }
  241. var expectedService = compose.ServiceStatus{
  242. ID: "myContainerID",
  243. Name: "myContainerID",
  244. Ports: []string{"42.42.42.42:80->80/tcp"},
  245. Replicas: 1,
  246. Desired: 1,
  247. }
  248. container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer, "eastus")
  249. assert.DeepEqual(t, container, expectedService)
  250. }
  251. func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
  252. project := types.Project{
  253. Services: []types.ServiceConfig{
  254. {
  255. Name: "service1",
  256. Image: "image1",
  257. },
  258. {
  259. Name: "service2",
  260. Image: "image2",
  261. },
  262. },
  263. }
  264. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  265. assert.NilError(t, err)
  266. assert.Assert(t, is.Len(*group.Containers, 3))
  267. assert.Equal(t, *(*group.Containers)[0].Name, "service1")
  268. assert.Equal(t, *(*group.Containers)[1].Name, "service2")
  269. assert.Equal(t, *(*group.Containers)[2].Name, ComposeDNSSidecarName)
  270. assert.DeepEqual(t, *(*group.Containers)[2].Command, []string{"/hosts", "service1", "service2"})
  271. assert.Equal(t, *(*group.Containers)[0].Image, "image1")
  272. assert.Equal(t, *(*group.Containers)[1].Image, "image2")
  273. assert.Equal(t, *(*group.Containers)[2].Image, dnsSidecarImage)
  274. }
  275. func TestComposeSingleContainerGroupToContainerNoDnsSideCarSide(t *testing.T) {
  276. project := types.Project{
  277. Services: []types.ServiceConfig{
  278. {
  279. Name: "service1",
  280. Image: "image1",
  281. },
  282. },
  283. }
  284. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  285. assert.NilError(t, err)
  286. assert.Assert(t, is.Len(*group.Containers, 1))
  287. assert.Equal(t, *(*group.Containers)[0].Name, "service1")
  288. assert.Equal(t, *(*group.Containers)[0].Image, "image1")
  289. }
  290. func TestLabelsErrorMessage(t *testing.T) {
  291. project := types.Project{
  292. Services: []types.ServiceConfig{
  293. {
  294. Name: "service1",
  295. Image: "image1",
  296. Labels: map[string]string{
  297. "label1": "value1",
  298. },
  299. },
  300. },
  301. }
  302. _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  303. assert.Error(t, err, "ACI integration does not support labels in compose applications")
  304. }
  305. func TestComposeContainerGroupToContainerWithDomainName(t *testing.T) {
  306. project := types.Project{
  307. Services: []types.ServiceConfig{
  308. {
  309. Name: "service1",
  310. Image: "image1",
  311. Ports: []types.ServicePortConfig{
  312. {
  313. Published: 80,
  314. Target: 80,
  315. },
  316. },
  317. DomainName: "myApp",
  318. },
  319. {
  320. Name: "service2",
  321. Image: "image2",
  322. Ports: []types.ServicePortConfig{
  323. {
  324. Published: 8080,
  325. Target: 8080,
  326. },
  327. },
  328. },
  329. },
  330. }
  331. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  332. assert.NilError(t, err)
  333. assert.Assert(t, is.Len(*group.Containers, 3))
  334. groupPorts := *group.IPAddress.Ports
  335. assert.Assert(t, is.Len(groupPorts, 2))
  336. assert.Equal(t, *groupPorts[0].Port, int32(80))
  337. assert.Equal(t, *groupPorts[1].Port, int32(8080))
  338. assert.Equal(t, *group.IPAddress.DNSNameLabel, "myApp")
  339. }
  340. func TestComposeContainerGroupToContainerErrorWhenSeveralDomainNames(t *testing.T) {
  341. project := types.Project{
  342. Services: []types.ServiceConfig{
  343. {
  344. Name: "service1",
  345. Image: "image1",
  346. DomainName: "myApp",
  347. },
  348. {
  349. Name: "service2",
  350. Image: "image2",
  351. DomainName: "myApp2",
  352. },
  353. },
  354. }
  355. _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  356. assert.Error(t, err, "ACI integration does not support specifying different domain names on services in the same compose application")
  357. }
  358. // ACI fails if group definition IPAddress has no ports
  359. func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing.T) {
  360. project := types.Project{
  361. Services: []types.ServiceConfig{
  362. {
  363. Name: "service1",
  364. Image: "image1",
  365. DomainName: "myApp",
  366. },
  367. {
  368. Name: "service2",
  369. Image: "image2",
  370. DomainName: "myApp",
  371. },
  372. },
  373. }
  374. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  375. assert.NilError(t, err)
  376. assert.Assert(t, is.Len(*group.Containers, 3))
  377. assert.Assert(t, group.IPAddress == nil)
  378. }
  379. var _0_1Gb = gbToBytes(0.1)
  380. func TestComposeContainerGroupToContainerResourceRequests(t *testing.T) {
  381. project := types.Project{
  382. Services: []types.ServiceConfig{
  383. {
  384. Name: "service1",
  385. Image: "image1",
  386. Deploy: &types.DeployConfig{
  387. Resources: types.Resources{
  388. Reservations: &types.Resource{
  389. NanoCPUs: "0.1",
  390. MemoryBytes: types.UnitBytes(_0_1Gb),
  391. },
  392. },
  393. },
  394. },
  395. },
  396. }
  397. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  398. assert.NilError(t, err)
  399. request := *((*group.Containers)[0]).Resources.Requests
  400. assert.Equal(t, *request.CPU, float64(0.1))
  401. assert.Equal(t, *request.MemoryInGB, float64(0.1))
  402. limits := *((*group.Containers)[0]).Resources.Limits
  403. assert.Equal(t, *limits.CPU, float64(0.1))
  404. assert.Equal(t, *limits.MemoryInGB, float64(0.1))
  405. }
  406. func TestComposeContainerGroupToContainerResourceRequestsAndLimits(t *testing.T) {
  407. project := types.Project{
  408. Services: []types.ServiceConfig{
  409. {
  410. Name: "service1",
  411. Image: "image1",
  412. Deploy: &types.DeployConfig{
  413. Resources: types.Resources{
  414. Reservations: &types.Resource{
  415. NanoCPUs: "0.1",
  416. MemoryBytes: types.UnitBytes(_0_1Gb),
  417. },
  418. Limits: &types.Resource{
  419. NanoCPUs: "0.3",
  420. MemoryBytes: types.UnitBytes(2 * _0_1Gb),
  421. },
  422. },
  423. },
  424. },
  425. },
  426. }
  427. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  428. assert.NilError(t, err)
  429. request := *((*group.Containers)[0]).Resources.Requests
  430. assert.Equal(t, *request.CPU, float64(0.1))
  431. assert.Equal(t, *request.MemoryInGB, float64(0.1))
  432. limits := *((*group.Containers)[0]).Resources.Limits
  433. assert.Equal(t, *limits.CPU, float64(0.3))
  434. assert.Equal(t, *limits.MemoryInGB, float64(0.2))
  435. }
  436. func TestComposeContainerGroupToContainerResourceLimitsOnly(t *testing.T) {
  437. project := types.Project{
  438. Services: []types.ServiceConfig{
  439. {
  440. Name: "service1",
  441. Image: "image1",
  442. Deploy: &types.DeployConfig{
  443. Resources: types.Resources{
  444. Limits: &types.Resource{
  445. NanoCPUs: "0.3",
  446. MemoryBytes: types.UnitBytes(2 * _0_1Gb),
  447. },
  448. },
  449. },
  450. },
  451. },
  452. }
  453. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  454. assert.NilError(t, err)
  455. request := *((*group.Containers)[0]).Resources.Requests
  456. assert.Equal(t, *request.CPU, float64(0.3))
  457. assert.Equal(t, *request.MemoryInGB, float64(0.2))
  458. limits := *((*group.Containers)[0]).Resources.Limits
  459. assert.Equal(t, *limits.CPU, float64(0.3))
  460. assert.Equal(t, *limits.MemoryInGB, float64(0.2))
  461. }
  462. func TestComposeContainerGroupToContainerResourceRequestsDefaults(t *testing.T) {
  463. project := types.Project{
  464. Services: []types.ServiceConfig{
  465. {
  466. Name: "service1",
  467. Image: "image1",
  468. Deploy: &types.DeployConfig{
  469. Resources: types.Resources{
  470. Reservations: &types.Resource{
  471. NanoCPUs: "",
  472. MemoryBytes: 0,
  473. },
  474. },
  475. },
  476. },
  477. },
  478. }
  479. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  480. assert.NilError(t, err)
  481. request := *((*group.Containers)[0]).Resources.Requests
  482. assert.Equal(t, *request.CPU, float64(1))
  483. assert.Equal(t, *request.MemoryInGB, float64(1))
  484. }
  485. func TestComposeContainerGroupToContainerenvVar(t *testing.T) {
  486. err := os.Setenv("key2", "value2")
  487. assert.NilError(t, err)
  488. project := types.Project{
  489. Services: []types.ServiceConfig{
  490. {
  491. Name: "service1",
  492. Image: "image1",
  493. Environment: types.MappingWithEquals{
  494. "key1": to.StringPtr("value1"),
  495. "key2": nil,
  496. },
  497. },
  498. },
  499. }
  500. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  501. assert.NilError(t, err)
  502. envVars := *((*group.Containers)[0]).EnvironmentVariables
  503. assert.Assert(t, is.Len(envVars, 2))
  504. assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key1"), Value: to.StringPtr("value1")}))
  505. assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")}))
  506. }
  507. func TestConvertContainerGroupStatus(t *testing.T) {
  508. assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(to.StringPtr("Started"))))
  509. assert.Equal(t, "Terminated", GetStatus(container(to.StringPtr("Terminated")), group(to.StringPtr("Stopped"))))
  510. assert.Equal(t, "Node Stopped", GetStatus(container(nil), group(to.StringPtr("Stopped"))))
  511. assert.Equal(t, "Node Started", GetStatus(container(nil), group(to.StringPtr("Started"))))
  512. assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(nil)))
  513. assert.Equal(t, "Unknown", GetStatus(container(nil), group(nil)))
  514. }
  515. func container(status *string) containerinstance.Container {
  516. var state *containerinstance.ContainerState = nil
  517. if status != nil {
  518. state = &containerinstance.ContainerState{
  519. State: status,
  520. }
  521. }
  522. return containerinstance.Container{
  523. ContainerProperties: &containerinstance.ContainerProperties{
  524. InstanceView: &containerinstance.ContainerPropertiesInstanceView{
  525. CurrentState: state,
  526. },
  527. },
  528. }
  529. }
  530. func group(status *string) containerinstance.ContainerGroup {
  531. var view *containerinstance.ContainerGroupPropertiesInstanceView = nil
  532. if status != nil {
  533. view = &containerinstance.ContainerGroupPropertiesInstanceView{
  534. State: status,
  535. }
  536. }
  537. return containerinstance.ContainerGroup{
  538. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  539. InstanceView: view,
  540. },
  541. }
  542. }