balena-supervisor/test/04-service.spec.coffee
Cameron Diver b3192679c7
fix: Correctly compare and generate network membership aliases
Before this change, service name resolution would only occur in the
default network. This was because we were not explicitly adding aliases
of the service names to the aliases fields.

We also fix the comparison, which would do funny things based on
container IDs, which was correct but unnecessary.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-03-28 10:27:21 +00:00

379 lines
10 KiB
CoffeeScript

m = require 'mochainon'
{ expect } = m.chai
_ = require 'lodash'
{ Service } = require '../src/compose/service'
configs = {
simple: {
compose: require('./data/docker-states/simple/compose.json')
imageInfo: require('./data/docker-states/simple/imageInfo.json')
inspect: require('./data/docker-states/simple/inspect.json')
}
entrypoint: {
compose: require('./data/docker-states/entrypoint/compose.json')
imageInfo: require('./data/docker-states/entrypoint/imageInfo.json')
inspect: require('./data/docker-states/entrypoint/inspect.json')
}
}
describe 'compose/service', ->
it 'extends environment variables properly', ->
extendEnvVarsOpts = {
uuid: '1234'
appName: 'awesomeApp'
commit: 'abcdef'
name: 'awesomeDevice'
version: 'v1.0.0'
deviceType: 'raspberry-pi'
osVersion: 'Resin OS 2.0.2'
}
service = {
appId: '23'
releaseId: 2
serviceId: 3
imageId: 4
serviceName: 'serviceName'
environment:
FOO: 'bar'
A_VARIABLE: 'ITS_VALUE'
}
s = Service.fromComposeObject(service, extendEnvVarsOpts)
expect(s.config.environment).to.deep.equal({
FOO: 'bar'
A_VARIABLE: 'ITS_VALUE'
RESIN_APP_ID: '23'
RESIN_APP_NAME: 'awesomeApp'
RESIN_DEVICE_UUID: '1234'
RESIN_DEVICE_TYPE: 'raspberry-pi'
RESIN_HOST_OS_VERSION: 'Resin OS 2.0.2'
RESIN_SERVICE_NAME: 'serviceName'
RESIN_SUPERVISOR_VERSION: 'v1.0.0'
RESIN_APP_LOCK_PATH: '/tmp/balena/updates.lock'
RESIN_SERVICE_KILL_ME_PATH: '/tmp/balena/handover-complete'
RESIN: '1'
BALENA_APP_ID: '23'
BALENA_APP_NAME: 'awesomeApp'
BALENA_DEVICE_UUID: '1234'
BALENA_DEVICE_TYPE: 'raspberry-pi'
BALENA_HOST_OS_VERSION: 'Resin OS 2.0.2'
BALENA_SERVICE_NAME: 'serviceName'
BALENA_SUPERVISOR_VERSION: 'v1.0.0'
BALENA_APP_LOCK_PATH: '/tmp/balena/updates.lock'
BALENA_SERVICE_HANDOVER_COMPLETE_PATH: '/tmp/balena/handover-complete'
BALENA: '1'
USER: 'root'
})
it 'returns the correct default bind mounts', ->
s = Service.fromComposeObject({
appId: '1234'
serviceName: 'foo'
releaseId: 2
serviceId: 3
imageId: 4
}, { appName: 'foo' })
binds = Service.defaultBinds(s.appId, s.serviceName)
expect(binds).to.deep.equal([
'/tmp/balena-supervisor/services/1234/foo:/tmp/resin'
'/tmp/balena-supervisor/services/1234/foo:/tmp/balena'
])
it 'produces the correct port bindings and exposed ports', ->
s = Service.fromComposeObject({
appId: '1234'
serviceName: 'foo'
releaseId: 2
serviceId: 3
imageId: 4
expose: [
1000,
'243/udp'
],
ports: [
'2344'
'2345:2354'
'2346:2367/udp'
]
}, {
imageInfo: Config: {
ExposedPorts: {
'53/tcp': {}
'53/udp': {}
'2354/tcp': {}
}
}
})
ports = s.generateExposeAndPorts()
expect(ports.portBindings).to.deep.equal({
'2344/tcp': [{
HostIp: '',
HostPort: '2344'
}],
'2354/tcp': [{
HostIp: '',
HostPort: '2345'
}],
'2367/udp': [{
HostIp: '',
HostPort: '2346'
}]
})
expect(ports.exposedPorts).to.deep.equal({
'1000/tcp': {}
'243/udp': {}
'2344/tcp': {}
'2354/tcp': {}
'2367/udp': {}
'53/tcp': {}
'53/udp': {}
})
it 'correctly handles port ranges', ->
s = Service.fromComposeObject({
appId: '1234'
serviceName: 'foo'
releaseId: 2
serviceId: 3
imageId: 4
expose: [
1000,
'243/udp'
],
ports: [
'1000-1003:2000-2003'
]
}, { appName: 'test' })
ports = s.generateExposeAndPorts()
expect(ports.portBindings).to.deep.equal({
'2000/tcp': [
HostIp: ''
HostPort: '1000'
],
'2001/tcp': [
HostIp: ''
HostPort: '1001'
],
'2002/tcp': [
HostIp: ''
HostPort: '1002'
],
'2003/tcp': [
HostIp: ''
HostPort: '1003'
]
})
expect(ports.exposedPorts).to.deep.equal({
'1000/tcp': {}
'2000/tcp': {}
'2001/tcp': {}
'2002/tcp': {}
'2003/tcp': {}
'243/udp': {}
})
it 'should correctly handle large port ranges', ->
@timeout(60000)
s = Service.fromComposeObject({
appId: '1234'
serviceName: 'foo'
releaseId: 2
serviceId: 3
imageId: 4
ports: [
'5-65536:5-65536/tcp'
'5-65536:5-65536/udp'
]
}, { appName: 'test' })
expect(s.generateExposeAndPorts()).to.not.throw
it 'should correctly report implied exposed ports from portMappings', ->
service = Service.fromComposeObject({
appId: 123456,
serviceId: 123456,
serviceName: 'test',
ports: [
'80:80'
'100:100'
]
}, { appName: 'test' })
expect(service.config).to.have.property('expose').that.deep.equals(['80/tcp', '100/tcp'])
describe 'parseMemoryNumber()', ->
makeComposeServiceWithLimit = (memLimit) ->
Service.fromComposeObject({
appId: 123456
serviceId: 123456
serviceName: 'foobar'
mem_limit: memLimit
}, { appName: 'test' })
it 'should correctly parse memory number strings without a unit', ->
expect(makeComposeServiceWithLimit('64').config.memLimit).to.equal(64)
it 'should correctly apply the default value', ->
expect(makeComposeServiceWithLimit(undefined).config.memLimit).to.equal(0)
it 'should correctly support parsing numbers as memory limits', ->
expect(makeComposeServiceWithLimit(64).config.memLimit).to.equal(64)
it 'should correctly parse memory number strings that use a byte unit', ->
expect(makeComposeServiceWithLimit('64b').config.memLimit).to.equal(64)
expect(makeComposeServiceWithLimit('64B').config.memLimit).to.equal(64)
it 'should correctly parse memory number strings that use a kilobyte unit', ->
expect(makeComposeServiceWithLimit('64k').config.memLimit).to.equal(65536)
expect(makeComposeServiceWithLimit('64K').config.memLimit).to.equal(65536)
expect(makeComposeServiceWithLimit('64kb').config.memLimit).to.equal(65536)
expect(makeComposeServiceWithLimit('64Kb').config.memLimit).to.equal(65536)
it 'should correctly parse memory number strings that use a megabyte unit', ->
expect(makeComposeServiceWithLimit('64m').config.memLimit).to.equal(67108864)
expect(makeComposeServiceWithLimit('64M').config.memLimit).to.equal(67108864)
expect(makeComposeServiceWithLimit('64mb').config.memLimit).to.equal(67108864)
expect(makeComposeServiceWithLimit('64Mb').config.memLimit).to.equal(67108864)
it 'should correctly parse memory number strings that use a gigabyte unit', ->
expect(makeComposeServiceWithLimit('64g').config.memLimit).to.equal(68719476736)
expect(makeComposeServiceWithLimit('64G').config.memLimit).to.equal(68719476736)
expect(makeComposeServiceWithLimit('64gb').config.memLimit).to.equal(68719476736)
expect(makeComposeServiceWithLimit('64Gb').config.memLimit).to.equal(68719476736)
describe 'getWorkingDir', ->
makeComposeServiceWithWorkdir = (workdir) ->
Service.fromComposeObject({
appId: 123456,
serviceId: 123456,
serviceName: 'foobar'
workingDir: workdir
}, { appName: 'test' })
it 'should remove a trailing slash', ->
expect(makeComposeServiceWithWorkdir('/usr/src/app/').config.workingDir).to.equal('/usr/src/app')
expect(makeComposeServiceWithWorkdir('/').config.workingDir).to.equal('/')
expect(makeComposeServiceWithWorkdir('/usr/src/app').config.workingDir).to.equal('/usr/src/app')
expect(makeComposeServiceWithWorkdir('').config.workingDir).to.equal('')
describe 'Docker <-> Compose config', ->
omitConfigForComparison = (config) ->
return _.omit(config, ['running', 'networks'])
it 'should be identical when converting a simple service', ->
composeSvc = Service.fromComposeObject(configs.simple.compose, configs.simple.imageInfo)
dockerSvc = Service.fromDockerContainer(configs.simple.inspect)
composeConfig = omitConfigForComparison(composeSvc.config)
dockerConfig = omitConfigForComparison(dockerSvc.config)
expect(composeConfig).to.deep.equal(dockerConfig)
expect(dockerSvc.isEqualConfig(composeSvc)).to.be.true
it 'should correct convert formats with a null entrypoint', ->
composeSvc = Service.fromComposeObject(configs.entrypoint.compose, configs.entrypoint.imageInfo)
dockerSvc = Service.fromDockerContainer(configs.entrypoint.inspect)
composeConfig = omitConfigForComparison(composeSvc.config)
dockerConfig = omitConfigForComparison(dockerSvc.config)
expect(composeConfig).to.deep.equal(dockerConfig)
expect(dockerSvc.isEqualConfig(composeSvc)).to.equals(true)
describe 'Networks', ->
it 'should correctly convert networks from compose to docker format', ->
makeComposeServiceWithNetwork = (networks) ->
Service.fromComposeObject({
appId: 123456,
serviceId: 123456,
serviceName: 'test',
networks
}, { appName: 'test' })
expect(makeComposeServiceWithNetwork({
'balena': {
'ipv4Address': '1.2.3.4'
}
}).toDockerContainer({ deviceName: 'foo' }).NetworkingConfig).to.deep.equal({
EndpointsConfig: {
'123456_balena': {
IPAMConfig: {
IPv4Address: '1.2.3.4'
},
Aliases: [
'test'
]
}
}
})
expect(makeComposeServiceWithNetwork({
balena: {
aliases: [ 'test', '1123']
ipv4Address: '1.2.3.4'
ipv6Address: '5.6.7.8'
linkLocalIps: [ '123.123.123' ]
}
}).toDockerContainer({ deviceName: 'foo' }).NetworkingConfig).to.deep.equal({
EndpointsConfig: {
'123456_balena': {
IPAMConfig: {
IPv4Address: '1.2.3.4'
IPv6Address: '5.6.7.8'
LinkLocalIPs: [ '123.123.123' ]
}
Aliases: [ 'test', '1123' ]
}
}
})
it 'should correctly convert Docker format to service format', ->
dockerCfg = require('./data/docker-states/simple/inspect.json')
makeServiceFromDockerWithNetwork = (networks) ->
Service.fromDockerContainer(
newConfig = _.cloneDeep(dockerCfg)
newConfig.NetworkSettings = { Networks: networks }
)
expect(makeServiceFromDockerWithNetwork({
'123456_balena': {
IPAMConfig: {
IPv4Address: '1.2.3.4'
},
Aliases: []
}
}).config.networks).to.deep.equal({
'123456_balena': {
'ipv4Address': '1.2.3.4'
}
})
expect(makeServiceFromDockerWithNetwork({
'123456_balena': {
IPAMConfig: {
IPv4Address: '1.2.3.4'
IPv6Address: '5.6.7.8'
LinkLocalIps: [ '123.123.123' ]
}
Aliases: [ 'test', '1123' ]
}
}).config.networks).to.deep.equal({
'123456_balena': {
ipv4Address: '1.2.3.4'
ipv6Address: '5.6.7.8'
linkLocalIps: [ '123.123.123' ]
aliases: [ 'test', '1123' ]
}
})