convert_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  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. SuccessThreshold: 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.Equal(t, *(*group.Containers)[0].LivenessProbe.SuccessThreshold, int32(42))
  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 TestContainerGroupToServiceStatus(t *testing.T) {
  182. myContainerGroup := containerinstance.ContainerGroup{
  183. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  184. IPAddress: &containerinstance.IPAddress{
  185. Ports: &[]containerinstance.Port{{
  186. Port: to.Int32Ptr(80),
  187. }},
  188. IP: to.StringPtr("42.42.42.42"),
  189. },
  190. },
  191. }
  192. myContainer := containerinstance.Container{
  193. Name: to.StringPtr("myContainerID"),
  194. ContainerProperties: &containerinstance.ContainerProperties{
  195. Ports: &[]containerinstance.ContainerPort{{
  196. Port: to.Int32Ptr(80),
  197. }},
  198. InstanceView: &containerinstance.ContainerPropertiesInstanceView{
  199. RestartCount: nil,
  200. CurrentState: &containerinstance.ContainerState{
  201. State: to.StringPtr("Running"),
  202. },
  203. },
  204. },
  205. }
  206. var expectedService = compose.ServiceStatus{
  207. ID: "myContainerID",
  208. Name: "myContainerID",
  209. Ports: []string{"42.42.42.42:80->80/tcp"},
  210. Replicas: 1,
  211. Desired: 1,
  212. }
  213. container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer, "eastus")
  214. assert.DeepEqual(t, container, expectedService)
  215. }
  216. func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
  217. project := types.Project{
  218. Services: []types.ServiceConfig{
  219. {
  220. Name: "service1",
  221. Image: "image1",
  222. },
  223. {
  224. Name: "service2",
  225. Image: "image2",
  226. },
  227. },
  228. }
  229. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  230. assert.NilError(t, err)
  231. assert.Assert(t, is.Len(*group.Containers, 3))
  232. assert.Equal(t, *(*group.Containers)[0].Name, "service1")
  233. assert.Equal(t, *(*group.Containers)[1].Name, "service2")
  234. assert.Equal(t, *(*group.Containers)[2].Name, ComposeDNSSidecarName)
  235. assert.DeepEqual(t, *(*group.Containers)[2].Command, []string{"/hosts", "service1", "service2"})
  236. assert.Equal(t, *(*group.Containers)[0].Image, "image1")
  237. assert.Equal(t, *(*group.Containers)[1].Image, "image2")
  238. assert.Equal(t, *(*group.Containers)[2].Image, dnsSidecarImage)
  239. }
  240. func TestComposeSingleContainerGroupToContainerNoDnsSideCarSide(t *testing.T) {
  241. project := types.Project{
  242. Services: []types.ServiceConfig{
  243. {
  244. Name: "service1",
  245. Image: "image1",
  246. },
  247. },
  248. }
  249. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  250. assert.NilError(t, err)
  251. assert.Assert(t, is.Len(*group.Containers, 1))
  252. assert.Equal(t, *(*group.Containers)[0].Name, "service1")
  253. assert.Equal(t, *(*group.Containers)[0].Image, "image1")
  254. }
  255. func TestLabelsErrorMessage(t *testing.T) {
  256. project := types.Project{
  257. Services: []types.ServiceConfig{
  258. {
  259. Name: "service1",
  260. Image: "image1",
  261. Labels: map[string]string{
  262. "label1": "value1",
  263. },
  264. },
  265. },
  266. }
  267. _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  268. assert.Error(t, err, "ACI integration does not support labels in compose applications")
  269. }
  270. func TestComposeContainerGroupToContainerWithDomainName(t *testing.T) {
  271. project := types.Project{
  272. Services: []types.ServiceConfig{
  273. {
  274. Name: "service1",
  275. Image: "image1",
  276. Ports: []types.ServicePortConfig{
  277. {
  278. Published: 80,
  279. Target: 80,
  280. },
  281. },
  282. DomainName: "myApp",
  283. },
  284. {
  285. Name: "service2",
  286. Image: "image2",
  287. Ports: []types.ServicePortConfig{
  288. {
  289. Published: 8080,
  290. Target: 8080,
  291. },
  292. },
  293. },
  294. },
  295. }
  296. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  297. assert.NilError(t, err)
  298. assert.Assert(t, is.Len(*group.Containers, 3))
  299. groupPorts := *group.IPAddress.Ports
  300. assert.Assert(t, is.Len(groupPorts, 2))
  301. assert.Equal(t, *groupPorts[0].Port, int32(80))
  302. assert.Equal(t, *groupPorts[1].Port, int32(8080))
  303. assert.Equal(t, *group.IPAddress.DNSNameLabel, "myApp")
  304. }
  305. func TestComposeContainerGroupToContainerErrorWhenSeveralDomainNames(t *testing.T) {
  306. project := types.Project{
  307. Services: []types.ServiceConfig{
  308. {
  309. Name: "service1",
  310. Image: "image1",
  311. DomainName: "myApp",
  312. },
  313. {
  314. Name: "service2",
  315. Image: "image2",
  316. DomainName: "myApp2",
  317. },
  318. },
  319. }
  320. _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  321. assert.Error(t, err, "ACI integration does not support specifying different domain names on services in the same compose application")
  322. }
  323. // ACI fails if group definition IPAddress has no ports
  324. func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing.T) {
  325. project := types.Project{
  326. Services: []types.ServiceConfig{
  327. {
  328. Name: "service1",
  329. Image: "image1",
  330. DomainName: "myApp",
  331. },
  332. {
  333. Name: "service2",
  334. Image: "image2",
  335. DomainName: "myApp",
  336. },
  337. },
  338. }
  339. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  340. assert.NilError(t, err)
  341. assert.Assert(t, is.Len(*group.Containers, 3))
  342. assert.Assert(t, group.IPAddress == nil)
  343. }
  344. var _0_1Gb = gbToBytes(0.1)
  345. func TestComposeContainerGroupToContainerResourceRequests(t *testing.T) {
  346. project := types.Project{
  347. Services: []types.ServiceConfig{
  348. {
  349. Name: "service1",
  350. Image: "image1",
  351. Deploy: &types.DeployConfig{
  352. Resources: types.Resources{
  353. Reservations: &types.Resource{
  354. NanoCPUs: "0.1",
  355. MemoryBytes: types.UnitBytes(_0_1Gb),
  356. },
  357. },
  358. },
  359. },
  360. },
  361. }
  362. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  363. assert.NilError(t, err)
  364. request := *((*group.Containers)[0]).Resources.Requests
  365. assert.Equal(t, *request.CPU, float64(0.1))
  366. assert.Equal(t, *request.MemoryInGB, float64(0.1))
  367. limits := *((*group.Containers)[0]).Resources.Limits
  368. assert.Equal(t, *limits.CPU, float64(0.1))
  369. assert.Equal(t, *limits.MemoryInGB, float64(0.1))
  370. }
  371. func TestComposeContainerGroupToContainerResourceRequestsAndLimits(t *testing.T) {
  372. project := types.Project{
  373. Services: []types.ServiceConfig{
  374. {
  375. Name: "service1",
  376. Image: "image1",
  377. Deploy: &types.DeployConfig{
  378. Resources: types.Resources{
  379. Reservations: &types.Resource{
  380. NanoCPUs: "0.1",
  381. MemoryBytes: types.UnitBytes(_0_1Gb),
  382. },
  383. Limits: &types.Resource{
  384. NanoCPUs: "0.3",
  385. MemoryBytes: types.UnitBytes(2 * _0_1Gb),
  386. },
  387. },
  388. },
  389. },
  390. },
  391. }
  392. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  393. assert.NilError(t, err)
  394. request := *((*group.Containers)[0]).Resources.Requests
  395. assert.Equal(t, *request.CPU, float64(0.1))
  396. assert.Equal(t, *request.MemoryInGB, float64(0.1))
  397. limits := *((*group.Containers)[0]).Resources.Limits
  398. assert.Equal(t, *limits.CPU, float64(0.3))
  399. assert.Equal(t, *limits.MemoryInGB, float64(0.2))
  400. }
  401. func TestComposeContainerGroupToContainerResourceLimitsOnly(t *testing.T) {
  402. project := types.Project{
  403. Services: []types.ServiceConfig{
  404. {
  405. Name: "service1",
  406. Image: "image1",
  407. Deploy: &types.DeployConfig{
  408. Resources: types.Resources{
  409. Limits: &types.Resource{
  410. NanoCPUs: "0.3",
  411. MemoryBytes: types.UnitBytes(2 * _0_1Gb),
  412. },
  413. },
  414. },
  415. },
  416. },
  417. }
  418. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  419. assert.NilError(t, err)
  420. request := *((*group.Containers)[0]).Resources.Requests
  421. assert.Equal(t, *request.CPU, float64(0.3))
  422. assert.Equal(t, *request.MemoryInGB, float64(0.2))
  423. limits := *((*group.Containers)[0]).Resources.Limits
  424. assert.Equal(t, *limits.CPU, float64(0.3))
  425. assert.Equal(t, *limits.MemoryInGB, float64(0.2))
  426. }
  427. func TestComposeContainerGroupToContainerResourceRequestsDefaults(t *testing.T) {
  428. project := types.Project{
  429. Services: []types.ServiceConfig{
  430. {
  431. Name: "service1",
  432. Image: "image1",
  433. Deploy: &types.DeployConfig{
  434. Resources: types.Resources{
  435. Reservations: &types.Resource{
  436. NanoCPUs: "",
  437. MemoryBytes: 0,
  438. },
  439. },
  440. },
  441. },
  442. },
  443. }
  444. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  445. assert.NilError(t, err)
  446. request := *((*group.Containers)[0]).Resources.Requests
  447. assert.Equal(t, *request.CPU, float64(1))
  448. assert.Equal(t, *request.MemoryInGB, float64(1))
  449. }
  450. func TestComposeContainerGroupToContainerenvVar(t *testing.T) {
  451. err := os.Setenv("key2", "value2")
  452. assert.NilError(t, err)
  453. project := types.Project{
  454. Services: []types.ServiceConfig{
  455. {
  456. Name: "service1",
  457. Image: "image1",
  458. Environment: types.MappingWithEquals{
  459. "key1": to.StringPtr("value1"),
  460. "key2": nil,
  461. },
  462. },
  463. },
  464. }
  465. group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
  466. assert.NilError(t, err)
  467. envVars := *((*group.Containers)[0]).EnvironmentVariables
  468. assert.Assert(t, is.Len(envVars, 2))
  469. assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key1"), Value: to.StringPtr("value1")}))
  470. assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")}))
  471. }
  472. func TestConvertContainerGroupStatus(t *testing.T) {
  473. assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(to.StringPtr("Started"))))
  474. assert.Equal(t, "Terminated", GetStatus(container(to.StringPtr("Terminated")), group(to.StringPtr("Stopped"))))
  475. assert.Equal(t, "Node Stopped", GetStatus(container(nil), group(to.StringPtr("Stopped"))))
  476. assert.Equal(t, "Node Started", GetStatus(container(nil), group(to.StringPtr("Started"))))
  477. assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(nil)))
  478. assert.Equal(t, "Unknown", GetStatus(container(nil), group(nil)))
  479. }
  480. func container(status *string) containerinstance.Container {
  481. var state *containerinstance.ContainerState = nil
  482. if status != nil {
  483. state = &containerinstance.ContainerState{
  484. State: status,
  485. }
  486. }
  487. return containerinstance.Container{
  488. ContainerProperties: &containerinstance.ContainerProperties{
  489. InstanceView: &containerinstance.ContainerPropertiesInstanceView{
  490. CurrentState: state,
  491. },
  492. },
  493. }
  494. }
  495. func group(status *string) containerinstance.ContainerGroup {
  496. var view *containerinstance.ContainerGroupPropertiesInstanceView = nil
  497. if status != nil {
  498. view = &containerinstance.ContainerGroupPropertiesInstanceView{
  499. State: status,
  500. }
  501. }
  502. return containerinstance.ContainerGroup{
  503. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  504. InstanceView: view,
  505. },
  506. }
  507. }