2021-04-26 19:54:04 +00:00
|
|
|
import { expect } from 'chai';
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
import * as sinon from 'sinon';
|
2021-04-09 17:03:52 +00:00
|
|
|
|
2021-04-26 19:54:04 +00:00
|
|
|
import { Network } from '../../../src/compose/network';
|
2021-04-26 16:06:04 +00:00
|
|
|
import { NetworkInspectInfo } from 'dockerode';
|
2021-05-05 20:32:31 +00:00
|
|
|
import { createNetwork, withMockerode } from '../../lib/mockerode';
|
2021-04-09 17:03:52 +00:00
|
|
|
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
import { log } from '../../../src/lib/supervisor-console';
|
|
|
|
|
2021-04-26 16:06:04 +00:00
|
|
|
describe('compose/network', () => {
|
|
|
|
describe('creating a network from a compose object', () => {
|
2021-04-09 17:03:52 +00:00
|
|
|
it('creates a default network configuration if no config is given', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{},
|
|
|
|
);
|
2021-04-09 17:03:52 +00:00
|
|
|
|
|
|
|
expect(network.name).to.equal('default');
|
|
|
|
expect(network.appId).to.equal(12345);
|
2021-08-25 23:25:47 +00:00
|
|
|
expect(network.appUuid).to.equal('deadbeef');
|
2021-04-09 17:03:52 +00:00
|
|
|
|
|
|
|
// Default configuration options
|
|
|
|
expect(network.config.driver).to.equal('bridge');
|
|
|
|
expect(network.config.ipam).to.deep.equal({
|
|
|
|
driver: 'default',
|
|
|
|
config: [],
|
|
|
|
options: {},
|
|
|
|
});
|
|
|
|
expect(network.config.enableIPv6).to.equal(false);
|
2021-08-25 23:25:47 +00:00
|
|
|
expect(network.config.labels).to.deep.equal({
|
|
|
|
'io.balena.app-id': '12345',
|
|
|
|
});
|
2021-04-09 17:03:52 +00:00
|
|
|
expect(network.config.options).to.deep.equal({});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('normalizes legacy labels', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-09 17:03:52 +00:00
|
|
|
labels: {
|
|
|
|
'io.resin.features.something': '1234',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(network.config.labels).to.deep.equal({
|
|
|
|
'io.balena.features.something': '1234',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '12345',
|
2021-04-09 17:03:52 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('accepts valid IPAM configurations', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network0 = Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-09 17:03:52 +00:00
|
|
|
ipam: { driver: 'dummy', config: [], options: {} },
|
|
|
|
});
|
|
|
|
|
|
|
|
// Default configuration options
|
|
|
|
expect(network0.config.ipam).to.deep.equal({
|
|
|
|
driver: 'dummy',
|
|
|
|
config: [],
|
|
|
|
options: {},
|
|
|
|
});
|
|
|
|
|
2021-08-25 23:25:47 +00:00
|
|
|
const network1 = Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-09 17:03:52 +00:00
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
|
|
|
ip_range: '172.20.10.0/24',
|
2021-04-26 16:06:04 +00:00
|
|
|
aux_addresses: { host0: '172.20.10.15', host1: '172.20.10.16' },
|
2021-04-09 17:03:52 +00:00
|
|
|
gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// Default configuration options
|
|
|
|
expect(network1.config.ipam).to.deep.equal({
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
2021-04-26 16:06:04 +00:00
|
|
|
ipRange: '172.20.10.0/24',
|
2021-04-09 17:03:52 +00:00
|
|
|
gateway: '172.20.0.1',
|
2021-04-26 16:06:04 +00:00
|
|
|
auxAddress: { host0: '172.20.10.15', host1: '172.20.10.16' },
|
2021-04-09 17:03:52 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
it('warns about IPAM configuration without both gateway and subnet', () => {
|
|
|
|
const logSpy = sinon.spy(log, 'warn');
|
|
|
|
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-05-05 20:32:31 +00:00
|
|
|
expect(logSpy).to.have.been.calledOnce;
|
|
|
|
expect(logSpy).to.have.been.calledWithMatch(
|
2021-04-09 17:03:52 +00:00
|
|
|
'Network IPAM config entries must have both a subnet and gateway',
|
|
|
|
);
|
|
|
|
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
logSpy.resetHistory();
|
|
|
|
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-05-05 20:32:31 +00:00
|
|
|
expect(logSpy).to.have.been.calledOnce;
|
|
|
|
expect(logSpy).to.have.been.calledWithMatch(
|
2021-04-09 17:03:52 +00:00
|
|
|
'Network IPAM config entries must have both a subnet and gateway',
|
|
|
|
);
|
Show warning instead of exception for invalid network config
A previous PR (#1656) fixed validation for network ipam config,
checking that both network and subnet are defined for each ipam config entry
(as described in the docker documentation).
After that PR, the validations throws an exception if the network target state is incorrect,
but this turns out to be the wrong approach, because that exception is also triggered
when querying target state.
This isn't a problem in normal operation, but it is in local mode, because local
mode queries the old target state before sending a new one. Since the query fails,
the CLI can never push the new target state.
This PR replaces the exception with a warning on the logs, since a
misconfigured network won't cause any engine failures, it will just
prevent containers to communicate through the provided network.
A future improvement should move this validation to an earlier point in the process,
so the target state can get rejected before it even gets to a point it
can be used.
Relates-to: #1693
Change-type: patch
2021-05-04 19:11:07 +00:00
|
|
|
|
|
|
|
logSpy.restore();
|
2021-04-09 17:03:52 +00:00
|
|
|
});
|
2021-05-06 14:50:11 +00:00
|
|
|
|
|
|
|
it('parses values from a compose object', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network1 = Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-05-06 14:50:11 +00:00
|
|
|
driver: 'bridge',
|
|
|
|
enable_ipv6: true,
|
|
|
|
internal: false,
|
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
options: {
|
|
|
|
'com.docker.ipam-option': 'abcd',
|
|
|
|
},
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.18.0.0/16',
|
|
|
|
gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
driver_opts: {
|
|
|
|
'com.docker.network-option': 'abcd',
|
|
|
|
},
|
|
|
|
labels: {
|
|
|
|
'com.docker.some-label': 'yes',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const dockerConfig = network1.toDockerConfig();
|
|
|
|
|
|
|
|
expect(dockerConfig.Driver).to.equal('bridge');
|
|
|
|
// Check duplicate forced to be true
|
|
|
|
expect(dockerConfig.CheckDuplicate).to.equal(true);
|
|
|
|
expect(dockerConfig.Internal).to.equal(false);
|
|
|
|
expect(dockerConfig.EnableIPv6).to.equal(true);
|
|
|
|
|
|
|
|
expect(dockerConfig.IPAM).to.deep.equal({
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {
|
|
|
|
'com.docker.ipam-option': 'abcd',
|
|
|
|
},
|
|
|
|
Config: [
|
|
|
|
{
|
|
|
|
Subnet: '172.18.0.0/16',
|
|
|
|
Gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(dockerConfig.Labels).to.deep.equal({
|
|
|
|
'io.balena.supervised': 'true',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '12345',
|
2021-05-06 14:50:11 +00:00
|
|
|
'com.docker.some-label': 'yes',
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(dockerConfig.Options).to.deep.equal({
|
|
|
|
'com.docker.network-option': 'abcd',
|
|
|
|
});
|
|
|
|
});
|
2021-04-09 17:03:52 +00:00
|
|
|
});
|
2021-04-26 16:06:04 +00:00
|
|
|
|
|
|
|
describe('creating a network from docker engine state', () => {
|
|
|
|
it('rejects networks without the proper name format', () => {
|
|
|
|
expect(() =>
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: 'abcd',
|
|
|
|
} as NetworkInspectInfo),
|
|
|
|
).to.throw();
|
|
|
|
|
|
|
|
expect(() =>
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: 'abcd_1234',
|
|
|
|
} as NetworkInspectInfo),
|
|
|
|
).to.throw();
|
|
|
|
|
|
|
|
expect(() =>
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: 'abcd_abcd',
|
|
|
|
} as NetworkInspectInfo),
|
|
|
|
).to.throw();
|
|
|
|
|
|
|
|
expect(() =>
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: '1234',
|
|
|
|
} as NetworkInspectInfo),
|
|
|
|
).to.throw();
|
2021-08-25 23:25:47 +00:00
|
|
|
|
|
|
|
expect(() =>
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: 'a173bdb734884b778f5cc3dffd18733e_default',
|
|
|
|
Labels: {}, // no app-id
|
|
|
|
} as NetworkInspectInfo),
|
|
|
|
).to.throw();
|
2021-04-26 16:06:04 +00:00
|
|
|
});
|
|
|
|
|
2021-08-25 23:25:47 +00:00
|
|
|
it('creates a network object from a legacy docker network configuration', () => {
|
2021-04-26 16:06:04 +00:00
|
|
|
const network = Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: '1234_default',
|
|
|
|
Driver: 'bridge',
|
|
|
|
EnableIPv6: true,
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {},
|
|
|
|
Config: [
|
|
|
|
{
|
|
|
|
Subnet: '172.18.0.0/16',
|
|
|
|
Gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
} as NetworkInspectInfo['IPAM'],
|
|
|
|
Internal: true,
|
|
|
|
Containers: {},
|
|
|
|
Options: {
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
} as NetworkInspectInfo['Options'],
|
|
|
|
Labels: {
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.supervised': 'true',
|
|
|
|
'io.balena.features.something': '123',
|
|
|
|
} as NetworkInspectInfo['Labels'],
|
|
|
|
} as NetworkInspectInfo);
|
|
|
|
|
|
|
|
expect(network.appId).to.equal(1234);
|
|
|
|
expect(network.name).to.equal('default');
|
|
|
|
expect(network.config.enableIPv6).to.equal(true);
|
|
|
|
expect(network.config.ipam.driver).to.equal('default');
|
|
|
|
expect(network.config.ipam.options).to.deep.equal({});
|
|
|
|
expect(network.config.ipam.config).to.deep.equal([
|
|
|
|
{
|
|
|
|
subnet: '172.18.0.0/16',
|
|
|
|
gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
expect(network.config.internal).to.equal(true);
|
|
|
|
expect(network.config.options).to.deep.equal({
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
});
|
|
|
|
expect(network.config.labels).to.deep.equal({
|
|
|
|
'io.balena.features.something': '123',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('creates a network object from a docker network configuration', () => {
|
|
|
|
const network = Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: 'a173bdb734884b778f5cc3dffd18733e_default',
|
|
|
|
Driver: 'bridge',
|
|
|
|
EnableIPv6: true,
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {},
|
|
|
|
Config: [
|
|
|
|
{
|
|
|
|
Subnet: '172.18.0.0/16',
|
|
|
|
Gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
} as NetworkInspectInfo['IPAM'],
|
|
|
|
Internal: true,
|
|
|
|
Containers: {},
|
|
|
|
Options: {
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
} as NetworkInspectInfo['Options'],
|
|
|
|
Labels: {
|
|
|
|
'io.balena.supervised': 'true',
|
2021-04-26 16:06:04 +00:00
|
|
|
'io.balena.features.something': '123',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '1234',
|
2021-04-26 16:06:04 +00:00
|
|
|
} as NetworkInspectInfo['Labels'],
|
|
|
|
} as NetworkInspectInfo);
|
|
|
|
|
|
|
|
expect(network.appId).to.equal(1234);
|
2021-08-25 23:25:47 +00:00
|
|
|
expect(network.appUuid).to.equal('a173bdb734884b778f5cc3dffd18733e');
|
2021-04-26 16:06:04 +00:00
|
|
|
expect(network.name).to.equal('default');
|
|
|
|
expect(network.config.enableIPv6).to.equal(true);
|
|
|
|
expect(network.config.ipam.driver).to.equal('default');
|
|
|
|
expect(network.config.ipam.options).to.deep.equal({});
|
|
|
|
expect(network.config.ipam.config).to.deep.equal([
|
|
|
|
{
|
|
|
|
subnet: '172.18.0.0/16',
|
|
|
|
gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
expect(network.config.internal).to.equal(true);
|
|
|
|
expect(network.config.options).to.deep.equal({
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
});
|
|
|
|
expect(network.config.labels).to.deep.equal({
|
|
|
|
'io.balena.features.something': '123',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '1234',
|
2021-04-26 16:06:04 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('normalizes legacy label names and excludes supervised label', () => {
|
|
|
|
const network = Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: '1234_default',
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {},
|
|
|
|
Config: [],
|
|
|
|
} as NetworkInspectInfo['IPAM'],
|
|
|
|
Labels: {
|
|
|
|
'io.resin.features.something': '123',
|
|
|
|
'io.balena.features.dummy': 'abc',
|
|
|
|
'io.balena.supervised': 'true',
|
|
|
|
} as NetworkInspectInfo['Labels'],
|
|
|
|
} as NetworkInspectInfo);
|
|
|
|
|
|
|
|
expect(network.config.labels).to.deep.equal({
|
|
|
|
'io.balena.features.something': '123',
|
|
|
|
'io.balena.features.dummy': 'abc',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('creating a network compose configuration from a network instance', () => {
|
|
|
|
it('creates a docker compose network object from the internal network config', () => {
|
|
|
|
const network = Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
2021-08-25 23:25:47 +00:00
|
|
|
Name: 'a173bdb734884b778f5cc3dffd18733e_default',
|
2021-04-26 16:06:04 +00:00
|
|
|
Driver: 'bridge',
|
|
|
|
EnableIPv6: true,
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {},
|
|
|
|
Config: [
|
|
|
|
{
|
|
|
|
Subnet: '172.18.0.0/16',
|
|
|
|
Gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
} as NetworkInspectInfo['IPAM'],
|
|
|
|
Internal: true,
|
|
|
|
Containers: {},
|
|
|
|
Options: {
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
} as NetworkInspectInfo['Options'],
|
|
|
|
Labels: {
|
|
|
|
'io.balena.features.something': '123',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '12345',
|
2021-04-26 16:06:04 +00:00
|
|
|
} as NetworkInspectInfo['Labels'],
|
|
|
|
} as NetworkInspectInfo);
|
|
|
|
|
2021-08-25 23:25:47 +00:00
|
|
|
expect(network.appId).to.equal(12345);
|
|
|
|
expect(network.appUuid).to.equal('a173bdb734884b778f5cc3dffd18733e');
|
|
|
|
|
2021-04-26 16:06:04 +00:00
|
|
|
// Convert to compose object
|
|
|
|
const compose = network.toComposeObject();
|
|
|
|
expect(compose.driver).to.equal('bridge');
|
|
|
|
expect(compose.driver_opts).to.deep.equal({
|
|
|
|
'com.docker.some-option': 'abcd',
|
|
|
|
});
|
|
|
|
expect(compose.enable_ipv6).to.equal(true);
|
|
|
|
expect(compose.internal).to.equal(true);
|
|
|
|
expect(compose.ipam).to.deep.equal({
|
|
|
|
driver: 'default',
|
|
|
|
options: {},
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.18.0.0/16',
|
|
|
|
gateway: '172.18.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
expect(compose.labels).to.deep.equal({
|
|
|
|
'io.balena.features.something': '123',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '12345',
|
2021-04-26 16:06:04 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('generateDockerName', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
it('creates a proper network name from the user given name and the app uuid', () => {
|
|
|
|
expect(Network.generateDockerName('deadbeef', 'default')).to.equal(
|
|
|
|
'deadbeef_default',
|
|
|
|
);
|
|
|
|
expect(Network.generateDockerName('deadbeef', 'bleh')).to.equal(
|
|
|
|
'deadbeef_bleh',
|
2021-04-26 16:06:04 +00:00
|
|
|
);
|
|
|
|
expect(Network.generateDockerName(1, 'default')).to.equal('1_default');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('comparing network configurations', () => {
|
|
|
|
it('ignores IPAM configuration', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-26 16:06:04 +00:00
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
|
|
|
ip_range: '172.20.10.0/24',
|
|
|
|
gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
expect(
|
2021-08-25 23:25:47 +00:00
|
|
|
network.isEqualConfig(
|
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {}),
|
|
|
|
),
|
2021-04-26 16:06:04 +00:00
|
|
|
).to.be.true;
|
|
|
|
|
|
|
|
// Only ignores ipam.config, not other ipam elements
|
|
|
|
expect(
|
|
|
|
network.isEqualConfig(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-26 16:06:04 +00:00
|
|
|
ipam: { driver: 'aaa' },
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
).to.be.false;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('compares configurations recursively', () => {
|
|
|
|
expect(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{},
|
|
|
|
).isEqualConfig(
|
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {}),
|
2021-04-26 16:06:04 +00:00
|
|
|
),
|
|
|
|
).to.be.true;
|
|
|
|
expect(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-26 16:06:04 +00:00
|
|
|
driver: 'default',
|
2021-08-25 23:25:47 +00:00
|
|
|
}).isEqualConfig(
|
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {}),
|
|
|
|
),
|
2021-04-26 16:06:04 +00:00
|
|
|
).to.be.false;
|
|
|
|
expect(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-26 16:06:04 +00:00
|
|
|
enable_ipv6: true,
|
2021-08-25 23:25:47 +00:00
|
|
|
}).isEqualConfig(
|
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {}),
|
|
|
|
),
|
2021-04-26 16:06:04 +00:00
|
|
|
).to.be.false;
|
|
|
|
expect(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
2021-04-26 16:06:04 +00:00
|
|
|
enable_ipv6: false,
|
|
|
|
internal: false,
|
|
|
|
}).isEqualConfig(
|
2021-08-25 23:25:47 +00:00
|
|
|
Network.fromComposeObject('default', 12345, 'deadbeef', {
|
|
|
|
internal: true,
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
).to.be.false;
|
|
|
|
|
|
|
|
// Comparison of a network without the app-uuid and a network
|
|
|
|
// with uuid has to return false
|
|
|
|
expect(
|
|
|
|
Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{},
|
|
|
|
).isEqualConfig(
|
|
|
|
Network.fromDockerNetwork({
|
|
|
|
Id: 'deadbeef',
|
|
|
|
Name: '12345_default',
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Options: {},
|
|
|
|
Config: [],
|
|
|
|
} as NetworkInspectInfo['IPAM'],
|
|
|
|
Labels: {
|
|
|
|
'io.balena.supervised': 'true',
|
|
|
|
} as NetworkInspectInfo['Labels'],
|
|
|
|
} as NetworkInspectInfo),
|
2021-04-26 16:06:04 +00:00
|
|
|
),
|
|
|
|
).to.be.false;
|
|
|
|
});
|
|
|
|
});
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
describe('creating networks', () => {
|
|
|
|
it('creates a new network on the engine with the given data', async () => {
|
|
|
|
await withMockerode(async (mockerode) => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{
|
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
|
|
|
ip_range: '172.20.10.0/24',
|
|
|
|
gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
2021-05-05 20:32:31 +00:00
|
|
|
},
|
2021-08-25 23:25:47 +00:00
|
|
|
);
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
// Create the network
|
|
|
|
await network.create();
|
|
|
|
|
|
|
|
// Check that the create function was called with proper arguments
|
|
|
|
expect(mockerode.createNetwork).to.have.been.calledOnceWith({
|
2021-08-25 23:25:47 +00:00
|
|
|
Name: 'deadbeef_default',
|
2021-05-05 20:32:31 +00:00
|
|
|
Driver: 'bridge',
|
|
|
|
CheckDuplicate: true,
|
|
|
|
IPAM: {
|
|
|
|
Driver: 'default',
|
|
|
|
Config: [
|
|
|
|
{
|
|
|
|
Subnet: '172.20.0.0/16',
|
|
|
|
IPRange: '172.20.10.0/24',
|
|
|
|
Gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
Options: {},
|
|
|
|
},
|
|
|
|
EnableIPv6: false,
|
|
|
|
Internal: false,
|
|
|
|
Labels: {
|
|
|
|
'io.balena.supervised': 'true',
|
2021-08-25 23:25:47 +00:00
|
|
|
'io.balena.app-id': '12345',
|
2021-05-05 20:32:31 +00:00
|
|
|
},
|
|
|
|
Options: {},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws the error if there is a problem while creating the network', async () => {
|
|
|
|
await withMockerode(async (mockerode) => {
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{
|
|
|
|
ipam: {
|
|
|
|
driver: 'default',
|
|
|
|
config: [
|
|
|
|
{
|
|
|
|
subnet: '172.20.0.0/16',
|
|
|
|
ip_range: '172.20.10.0/24',
|
|
|
|
gateway: '172.20.0.1',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
options: {},
|
|
|
|
},
|
2021-05-05 20:32:31 +00:00
|
|
|
},
|
2021-08-25 23:25:47 +00:00
|
|
|
);
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
// Re-define the dockerode.createNetwork to throw
|
|
|
|
mockerode.createNetwork.rejects('Unknown engine error');
|
|
|
|
|
|
|
|
// Creating the network should fail
|
|
|
|
return expect(network.create()).to.be.rejected.then((error) =>
|
|
|
|
expect(error).to.have.property('name', 'Unknown engine error'),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('removing a network', () => {
|
2021-08-25 23:25:47 +00:00
|
|
|
it('removes the legacy network from the engine if it exists', async () => {
|
|
|
|
// Create a mock network to add to the mock engine
|
|
|
|
const dockerNetwork = createNetwork({
|
|
|
|
Id: 'aaaaaaa',
|
|
|
|
Name: '12345_default',
|
|
|
|
});
|
|
|
|
|
|
|
|
await withMockerode(
|
|
|
|
async (mockerode) => {
|
|
|
|
// Check that the engine has the network
|
|
|
|
expect(await mockerode.listNetworks()).to.have.lengthOf(1);
|
|
|
|
|
|
|
|
// Create a dummy network object
|
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{},
|
|
|
|
);
|
|
|
|
|
|
|
|
// Perform the operation
|
|
|
|
await network.remove();
|
|
|
|
|
|
|
|
// The removal step should delete the object from the engine data
|
|
|
|
expect(mockerode.removeNetwork).to.have.been.calledOnceWith(
|
|
|
|
'aaaaaaa',
|
|
|
|
);
|
|
|
|
},
|
|
|
|
{ networks: [dockerNetwork] },
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2021-05-05 20:32:31 +00:00
|
|
|
it('removes the network from the engine if it exists', async () => {
|
|
|
|
// Create a mock network to add to the mock engine
|
|
|
|
const dockerNetwork = createNetwork({
|
|
|
|
Id: 'deadbeef',
|
2021-08-25 23:25:47 +00:00
|
|
|
Name: 'a173bdb734884b778f5cc3dffd18733e_default',
|
|
|
|
Labels: {
|
|
|
|
'io.balena.supervised': 'true',
|
|
|
|
'io.balena.app-id': '12345',
|
|
|
|
},
|
2021-05-05 20:32:31 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
await withMockerode(
|
|
|
|
async (mockerode) => {
|
|
|
|
// Check that the engine has the network
|
|
|
|
expect(await mockerode.listNetworks()).to.have.lengthOf(1);
|
|
|
|
|
|
|
|
// Create a dummy network object
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'a173bdb734884b778f5cc3dffd18733e',
|
|
|
|
{},
|
|
|
|
);
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
// Perform the operation
|
|
|
|
await network.remove();
|
|
|
|
|
|
|
|
// The removal step should delete the object from the engine data
|
|
|
|
expect(mockerode.removeNetwork).to.have.been.calledOnceWith(
|
|
|
|
'deadbeef',
|
|
|
|
);
|
|
|
|
},
|
|
|
|
{ networks: [dockerNetwork] },
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('ignores the request if the given network does not exist on the engine', async () => {
|
|
|
|
// Create a mock network to add to the mock engine
|
|
|
|
const mockNetwork = createNetwork({
|
2021-08-25 23:25:47 +00:00
|
|
|
Id: 'aaaaaaaa',
|
2021-05-05 20:32:31 +00:00
|
|
|
Name: 'some_network',
|
|
|
|
});
|
|
|
|
|
|
|
|
await withMockerode(
|
|
|
|
async (mockerode) => {
|
|
|
|
// Check that the engine has the network
|
|
|
|
expect(await mockerode.listNetworks()).to.have.lengthOf(1);
|
|
|
|
|
|
|
|
// Create a dummy network object
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'deadbeef',
|
|
|
|
{},
|
|
|
|
);
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
// This should not fail
|
|
|
|
await expect(network.remove()).to.not.be.rejected;
|
|
|
|
|
|
|
|
// We expect the network state to remain constant
|
|
|
|
expect(await mockerode.listNetworks()).to.have.lengthOf(1);
|
|
|
|
},
|
|
|
|
{ networks: [mockNetwork] },
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws the error if there is a problem while removing the network', async () => {
|
|
|
|
// Create a mock network to add to the mock engine
|
|
|
|
const mockNetwork = createNetwork({
|
2021-08-25 23:25:47 +00:00
|
|
|
Id: 'aaaaaaaa',
|
|
|
|
Name: 'a173bdb734884b778f5cc3dffd18733e_default',
|
|
|
|
Labels: {
|
|
|
|
'io.balena.app-id': '12345',
|
|
|
|
},
|
2021-05-05 20:32:31 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
await withMockerode(
|
|
|
|
async (mockerode) => {
|
|
|
|
// We can change the return value of the mockerode removeNetwork
|
|
|
|
// to have the remove operation fail
|
2021-08-25 23:25:47 +00:00
|
|
|
mockerode.removeNetwork.throws({
|
|
|
|
statusCode: 500,
|
|
|
|
message: 'Failed to remove the network',
|
|
|
|
});
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
// Create a dummy network object
|
2021-08-25 23:25:47 +00:00
|
|
|
const network = Network.fromComposeObject(
|
|
|
|
'default',
|
|
|
|
12345,
|
|
|
|
'a173bdb734884b778f5cc3dffd18733e',
|
|
|
|
{},
|
|
|
|
);
|
2021-05-05 20:32:31 +00:00
|
|
|
|
|
|
|
await expect(network.remove()).to.be.rejected;
|
|
|
|
},
|
|
|
|
{ networks: [mockNetwork] },
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2021-04-09 17:03:52 +00:00
|
|
|
});
|