diff --git a/.circleci/config.yml b/.circleci/config.yml index 550d4339d..67242dae6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -140,7 +140,7 @@ commands: steps: - get-code # note: network tests run inside docker - the dependencies will be fetched inside docker image. - run: - name: Running Network Tests (environment = << pipeline.parameters.environment >>) + name: Running Network Tests (environment = prod) command: scripts/circleci-run-tests.sh - save-test-results framework-tests: @@ -228,6 +228,7 @@ jobs: TEST_TYPE: "network" BVER: "stable" BROWSER: << parameters.browser >> + ENVIRONMENT: "prod" TOPOLOGY: << parameters.topology >> TEST_STABILITY: << parameters.test_stability >> executor: machine-executor diff --git a/docker/dockerProxyClient.js b/docker/dockerProxyClient.js index 3fd021a4c..d7117d67d 100644 --- a/docker/dockerProxyClient.js +++ b/docker/dockerProxyClient.js @@ -10,7 +10,7 @@ let requestNumber = 0; class DockerProxyClient { /** * Construct a new {@link DockerProxyClient}. - * @param {string} serverUrl - url pointing to an instance of {@link DockerProxyServer} + * @param {string} [serverUrl] - url pointing to an instance of {@link DockerProxyServer} */ constructor(serverUrl) { this._serverUrl = serverUrl || DOCKER_PROXY_SERVER_URL; @@ -155,12 +155,9 @@ class DockerProxyClient { const thisRequestNumber = requestNumber++; const logPrefix = `DockerProxyClient [${thisRequestNumber}]: `; try { - console.log(logPrefix + 'Requesting: ', api); const res = await fetch(this._serverUrl + api); const text = await res.text(); - const json = text ? JSON.parse(text) : {}; - console.log(logPrefix + 'Done: ', api, json); - return json; + return text ? JSON.parse(text) : {}; } catch (err) { console.error(logPrefix + 'Threw : ', err); throw err; diff --git a/docker/dockerProxyServer.js b/docker/dockerProxyServer.js index e7d6987cf..dd732f161 100644 --- a/docker/dockerProxyServer.js +++ b/docker/dockerProxyServer.js @@ -63,10 +63,8 @@ class DockerProxyServer { app.get(endpoint, async ({ params }, res, next) => { const thisRequestNumber = requestNumber++; const logPrefix = `DockerProxyServer [${thisRequestNumber}]: `; - console.log(logPrefix + 'Executing: ', endpoint, params); try { const data = await this[handleRequest](params); - console.log(logPrefix + 'Done executing: ', endpoint, params); return res.send(data); } catch (err) { console.error(logPrefix + 'Error executing: ', endpoint, params); @@ -76,10 +74,7 @@ class DockerProxyServer { }); return new Promise((resolve, reject) => { - this._server = app.listen(this._serverPort, () => { - console.log(`DockerProxyServer listening on port ${this._serverPort}!`); - resolve(); - }); + this._server = app.listen(this._serverPort, resolve); this._server.once('error', reject); }); } diff --git a/docker/fetchRequest.js b/docker/fetchRequest.js index 587a83714..9042eab6e 100644 --- a/docker/fetchRequest.js +++ b/docker/fetchRequest.js @@ -6,7 +6,6 @@ let requestId = 200; function fetchRequest(options, postData) { const thisRequest = requestId++; const logPrefix = `fetchRequest[${thisRequest}]: `; - console.log(logPrefix + 'Processing ', postData); return new Promise((resolve, reject) => { let clientRequest = http.request(options, res => { const requestFailed = res.statusCode !== 200 && res.statusCode !== 201; @@ -27,7 +26,6 @@ function fetchRequest(options, postData) { if (requestFailed) { console.warn(logPrefix + 'requestFailed2 returned:', res.statusCode, postData); } - console.log(logPrefix + 'resolving'); resolve(parsedData); } catch (e) { console.error(logPrefix + 'rejecting:', e); diff --git a/test/integration/spec/docker/reconnection.js b/test/integration/spec/docker/reconnection.js index b76342f7b..9dc31c566 100644 --- a/test/integration/spec/docker/reconnection.js +++ b/test/integration/spec/docker/reconnection.js @@ -10,7 +10,6 @@ const { createRoom, completeRoom } = require('../../../lib/rest'); const getToken = require('../../../lib/token'); const { - getRegionalizedIceServers, randomName, validateMediaFlow, waitToGoOffline, @@ -37,20 +36,41 @@ const RECONNECTED_TIMEOUT = 5 * ONE_MINUTE; const DISCONNECTED_TIMEOUT = 10 * ONE_MINUTE; const RESET_NETWORK_TIMEOUT = 4 * ONE_MINUTE; -const DOCKER_PROXY_TURN_REGIONS = ['au1', 'us1', 'us2']; +const DOCKER_PROXY_REGIONS = ['au1', 'us1', 'us2']; + +const DOCKER_PROXY_VSG_IP_RANGES = { + au1: [ + '52.65.124.94-52.65.124.94', + '54.79.169.174-54.79.169.174' + ], + us1: [ + '18.214.223.92-18.214.223.92', + '34.199.163.120-34.199.163.120', + '35.170.161.214-35.170.161.214' + ], + us2: [ + '44.240.99.173-44.240.99.173', + '44.241.246.215-44.241.246.215', + '54.214.154.157-54.214.154.157' + ] +}; + const DOCKER_PROXY_TURN_IP_RANGES = { au1: [ '13.210.2.128-13.210.2.159', - '54.252.254.64-54.252.254.127' + '54.252.254.64-54.252.254.127', + '3.25.42.128-3.25.42.255' ], us1: [ '34.203.254.0-34.203.254.255', '54.172.60.0-54.172.61.255', - '34.203.250.0-34.203.251.255' + '34.203.250.0-34.203.251.255', + '3.235.111.128-3.235.111.255' ], us2: [ '34.216.110.128-34.216.110.159', - '54.244.51.0-54.244.51.255' + '54.244.51.0-54.244.51.255', + '44.234.69.0-44.234.69.12' ] }; @@ -65,18 +85,22 @@ function waitWhileNotDisconnected(disconnectPromise, promiseOrArray, message, ti })]); } -// Resolves when room received n track started events. -function waitForTracksToStart(room, n) { - return n <= 0 ? Promise.resolve() : new Promise(resolve => { - room.on('trackStarted', function trackStarted() { - if (--n <= 0) { - room.removeListener('trackStarted', trackStarted); - resolve(); - } +function getStartedPublications(room) { + return flatMap(room.participants, participant => { + return Array.from(participant.tracks.values()).filter(({ track }) => { + return track && track.isStarted; }); }); } +// Resolves when room received n track started events. +async function waitForTracksToStart(room, n) { + while (getStartedPublications(room).length < n) { + /* eslint-disable no-await-in-loop */ + await waitOnceForRoomEvent(room, 'trackStarted'); + } +} + /** * Set up a Room for the given number of people. * @param {Array<{identity: string, options?: object, region?: string}>} setupOptions @@ -90,25 +114,23 @@ async function setup(setupOptions) { audio: true, fake: true, name: sid, - // logLevel: 'debug', video: smallVideoConstraints }, options, defaults); const token = getToken(identity); if (region) { - options.iceServers = await waitFor( - getRegionalizedIceServers(token, region, options), - `${sid}: get TURN servers regionalized to ${region}`); + options.region = region; options.iceTransportPolicy = 'relay'; } const room = await waitFor(connect(token, options), `${sid}: ${identity} connected`); - const { iceTransportPolicy, iceServers } = options; - const shouldWaitForTracksStarted = iceTransportPolicy !== 'relay' - || !Array.isArray(iceServers) - || iceServers.length > 0; + + const shouldWaitForTracksStarted = iceTransportPolicy !== 'relay' || ( + (!Array.isArray(iceServers) || iceServers.length > 0) + && defaults.topology !== 'peer-to-peer' + ); const nTracks = (setupOptions.length - 1) * 2; if (shouldWaitForTracksStarted) { @@ -125,12 +147,14 @@ function readCurrentNetworks(dockerAPI) { return waitFor(dockerAPI.getCurrentNetworks(), 'getCurrentNetworks'); } -describe('network:', function() { +describe('Network Reconnection', function() { this.retries(2); - this.timeout(8 * ONE_MINUTE); - let dockerAPI = new DockerProxyClient(); + this.timeout(10 * ONE_MINUTE); + let dockerAPI; let isRunningInsideDocker = false; + before(this.title, async function() { + dockerAPI = new DockerProxyClient(); isRunningInsideDocker = await dockerAPI.isDocker(); }); @@ -141,10 +165,9 @@ describe('network:', function() { }); it('connect rejects when network is down', async () => { - await waitFor(dockerAPI.resetNetwork(), 'reset network', RESET_NETWORK_TIMEOUT); - await waitToGoOnline(); - let currentNetworks = await readCurrentNetworks(dockerAPI); + const currentNetworks = await readCurrentNetworks(dockerAPI); const sid = await createRoom(name, defaults.topology); + const options = Object.assign({ audio: true, fake: true, @@ -155,37 +178,41 @@ describe('network:', function() { await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from all networks'); await waitToGoOffline(); - const start = new Date(); - let room = null; + let connectError; + let room; + try { room = await connect(getToken('Alice'), options); } catch (error) { - // this exception is expected. - const end = new Date(); - const seconds = (end.getTime() - start.getTime()) / 1000; - console.log(error.message, error.stack, error); - assert(error instanceof SignalingConnectionError || error instanceof MediaConnectionError); - console.log(`Connect rejected after ${seconds} seconds:`, error.message); - return; + connectError = error; } finally { - console.log('resetting network'); + if (room) { + room.disconnect(); + await completeRoom(room.sid); + } await waitFor(dockerAPI.resetNetwork(), 'resetting network', RESET_NETWORK_TIMEOUT); + await waitToGoOnline(); } - throw new Error(`Unexpectedly succeeded joining a room: ${room.sid}`); + assert(typeof room === 'undefined', `Unexpectedly joined a Room ${room && room.sid}`); + assert(connectError instanceof SignalingConnectionError + || connectError instanceof MediaConnectionError); }); - describe('turn region blocking tests (@unstable: JSDK-2810)', () => { + // TODO(mmalavalli): Investigate why this test is failing on Peer-to-Peer Rooms in Firefox. + (isFirefox && defaults.topology === 'peer-to-peer' ? describe.skip : describe)('Media Reconnection (@unstable: JSDK-2810)', () => { let rooms; let disconnected; before(this.title, async function() { if (!isRunningInsideDocker) { this.skip(); + return; } rooms = await setup(['Alice', 'Bob', 'Charlie'].map((identity, i) => { - return { identity, region: DOCKER_PROXY_TURN_REGIONS[i] }; + return { identity, region: DOCKER_PROXY_REGIONS[i] }; })); disconnected = Promise.all(rooms.map(room => new Promise(resolve => room.once('disconnected', resolve)))); + await waitWhileNotDisconnected(disconnected, rooms.map(room => validateMediaFlow(room)), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); }); after(async () => { @@ -195,27 +222,13 @@ describe('network:', function() { await completeRoom(rooms[0].sid); } rooms = null; + await waitFor(dockerAPI.resetNetwork(), 'resetting network', RESET_NETWORK_TIMEOUT); + await waitToGoOnline(); } }); - it('validate media flow', () => { - return waitWhileNotDisconnected(disconnected, rooms.map(validateMediaFlow), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); - }); - - it('block all TURN regions', async () => { - const reconnectingPromises = rooms.map(room => waitOnceForRoomEvent(room, 'reconnecting')); - const reconnectedPromises = rooms.map(room => waitOnceForRoomEvent(room, 'reconnected')); - - const ipRanges = flatMap(DOCKER_PROXY_TURN_REGIONS, region => DOCKER_PROXY_TURN_IP_RANGES[region]); - await dockerAPI.blockIpRanges(ipRanges); - await waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT); - - await dockerAPI.unblockIpRanges(ipRanges); - return waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT); - }); - - it('block specific TURN regions', async () => { - const turnRegionsToBlock = DOCKER_PROXY_TURN_REGIONS.slice(1); + it('the Rooms of Participants whose TURN regions are blocked should emit reconnecting and reconnected events', async () => { + const turnRegionsToBlock = DOCKER_PROXY_REGIONS.slice(1); const ipRanges = flatMap(turnRegionsToBlock, region => DOCKER_PROXY_TURN_IP_RANGES[region]); const blockedRooms = rooms.slice(1); const reconnectingPromises = blockedRooms.map(room => waitOnceForRoomEvent(room, 'reconnecting')); @@ -228,240 +241,233 @@ describe('network:', function() { }); }); + describe('Signaling Reconnection', () => { + let rooms = []; + let currentNetworks = null; + let disconnected; - [['Alice'], ['Alice', 'Bob']].forEach(identities => { - describe(`${identities.length} Participant(s)`, () => { - let rooms = []; - let currentNetworks = null; - let disconnected; - - beforeEach(async function() { - if (!isRunningInsideDocker) { - this.skip(); - } + beforeEach(async function() { + if (!isRunningInsideDocker) { + this.skip(); + return; + } + const setupOptions = [ + { identity: 'Alice', region: 'us1' }, + { identity: 'Bob', region: 'us2' } + ]; + currentNetworks = await readCurrentNetworks(dockerAPI); + rooms = await setup(setupOptions); + disconnected = Promise.all(rooms.map(room => new Promise(resolve => room.once('disconnected', resolve)))); + }); + afterEach(async () => { + let sid = null; + if (!window.navigator.onLine) { await waitFor(dockerAPI.resetNetwork(), 'reset network', RESET_NETWORK_TIMEOUT); await waitToGoOnline(); - currentNetworks = await readCurrentNetworks(dockerAPI); - const setupOptions = identities.map(identity => ({ identity })); - rooms = await setup(setupOptions); - disconnected = Promise.all(rooms.map(room => new Promise(resolve => room.once('disconnected', resolve)))); + } + rooms.forEach(room => { + if (room) { + room.disconnect(); + sid = room.sid; + } }); + rooms = []; + if (sid) { + await completeRoom(sid); + } + }); - afterEach(async () => { - let sid = null; - rooms.forEach(room => { - if (room) { - room.disconnect(); - sid = room.sid; - } - }); - rooms = []; - await waitFor(dockerAPI.resetNetwork(), 'reset network after each', RESET_NETWORK_TIMEOUT); - if (sid) { - await completeRoom(sid); + describe('Network interruption', () => { + let disconnectedPromises; + let localParticipantDisconnectedPromises; + let localParticipantReconnectedPromises; + let localParticipantReconnectingPromises; + let reconnectedPromises; + let reconnectingPromises; + + beforeEach(async function() { + if (!isRunningInsideDocker) { + this.skip(); + return; } + disconnectedPromises = rooms.map(room => new Promise(resolve => room.once('disconnected', resolve))); + localParticipantDisconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('disconnected', resolve))); + localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); + localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); + reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); + reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); + await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from all networks'); + await waitToGoOffline(); }); - describe('Network interruption', () => { - let disconnectedPromises; - let localParticipantDisconnectedPromises; - let localParticipantReconnectedPromises; - let localParticipantReconnectingPromises; - let reconnectedPromises; - let reconnectingPromises; - - beforeEach(async function() { - if (!isRunningInsideDocker) { - this.skip(); - } - disconnectedPromises = rooms.map(room => new Promise(resolve => room.once('disconnected', resolve))); - localParticipantDisconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('disconnected', resolve))); - localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); - localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); - reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); - reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); - await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from all networks'); - await waitToGoOffline(); - }); + it('should emit "reconnecting" on the Rooms and LocalParticipants', () => { + return Promise.all([ + waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), + waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT) + ]); + }); - it('should emit "reconnecting" on the Rooms and LocalParticipants', () => { - return Promise.all([ + context('that is longer than the session timeout', () => { + it(`should emit "disconnected" on the Rooms and LocalParticipants${isFirefox ? ' - @unstable: JSDK-2811' : ''}`, async () => { + await Promise.all([ waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT) ]); + return Promise.all([ + waitFor(localParticipantDisconnectedPromises, `localParticipantDisconnectedPromises: ${rooms[0].sid}`, DISCONNECTED_TIMEOUT), + waitFor(disconnectedPromises, `disconnectedPromises: ${rooms[0].sid}`, DISCONNECTED_TIMEOUT) + ]); }); + }); - context('that is longer than the session timeout', () => { - it(`should emit "disconnected" on the Rooms and LocalParticipants${isFirefox ? ' - @unstable: JSDK-2811' : ''}`, async () => { - await Promise.all([ - waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), - waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT) - ]); - return Promise.all([ - waitFor(localParticipantDisconnectedPromises, `localParticipantDisconnectedPromises: ${rooms[0].sid}`, DISCONNECTED_TIMEOUT), - waitFor(disconnectedPromises, `disconnectedPromises: ${rooms[0].sid}`, DISCONNECTED_TIMEOUT) - ]); - }); - }); - - context('that recovers before the session timeout', () => { - it('should emit "reconnected" on the Rooms and LocalParticipants (@unstable: JSDK-2812)', async () => { - await waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT); - await waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT); - - await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.connectToNetwork(networkId)), 'reconnect to original networks'); - await readCurrentNetworks(dockerAPI); - await waitToGoOnline(); + context('that recovers before the session timeout', () => { + it('should emit "reconnected" on the Rooms and LocalParticipants (@unstable: JSDK-2812)', async () => { + await waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT); + await waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT); - await waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT); - await waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT); + await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.connectToNetwork(networkId)), 'reconnect to original networks'); + await readCurrentNetworks(dockerAPI); + await waitToGoOnline(); - if (identities.length > 1) { - await waitWhileNotDisconnected(disconnected, rooms.map(validateMediaFlow), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); - } - }); + await waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT); + await waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT); + await waitWhileNotDisconnected(disconnected, rooms.map(room => validateMediaFlow(room)), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); }); }); + }); - // NOTE: network handoff does not work Firefox because of following known issues - // ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1546562)) - // ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1548318)) - (isFirefox ? describe.skip : describe)('Network handoff reconnects to new network', () => { - it('Scenario 1 (jump): connected interface switches off and then a new interface switches on (@unstable: JSDK-2813)', async () => { - const localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); - const localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); - const reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); - const reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); - const newNetwork = await waitFor(dockerAPI.createNetwork(), 'create network'); - - await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from networks'); - await waitToGoOffline(); - await waitFor(dockerAPI.connectToNetwork(newNetwork.Id), 'connect to network'); - await waitToGoOnline(); - console.log('current networks:', await readCurrentNetworks(dockerAPI)); + // NOTE: network handoff does not work Firefox because of following known issues + // ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1546562)) + // ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1548318)) + (isFirefox ? describe.skip : describe)('Network handoff reconnects to new network', () => { + it('Scenario 1 (jump): connected interface switches off and then a new interface switches on (@unstable: JSDK-2813)', async () => { + const localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); + const localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); + const reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); + const reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); + const newNetwork = await waitFor(dockerAPI.createNetwork(), 'create network'); + + await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from networks'); + await waitToGoOffline(); + await waitFor(dockerAPI.connectToNetwork(newNetwork.Id), 'connect to network'); + await waitToGoOnline(); + console.log('current networks:', await readCurrentNetworks(dockerAPI)); + + await Promise.all([ + waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), + waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT) + ]); + await Promise.all([ + waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT), + waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT) + ]); + await waitWhileNotDisconnected(disconnected, rooms.map(room => validateMediaFlow(room)), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); + }); - await Promise.all([ - waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), - waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT) - ]); - await Promise.all([ - waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT), - waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT) - ]); + it('Scenario 2 (step) : new interface switches on and then the connected interface switches off (@unstable: JSDK-2814) ', async () => { + const localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); + const localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); + const reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); + const reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); - if (identities.length > 1) { - await waitWhileNotDisconnected(disconnected, rooms.map(validateMediaFlow), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); - } - }); + // create and connect to new network + const newNetwork = await waitFor(dockerAPI.createNetwork(), 'create network'); + await waitFor(dockerAPI.connectToNetwork(newNetwork.Id), 'connect to network'); + await readCurrentNetworks(dockerAPI); - it('Scenario 2 (step) : new interface switches on and then the connected interface switches off (@unstable: JSDK-2814) ', async () => { - const localParticipantReconnectedPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnected', resolve))); - const localParticipantReconnectingPromises = rooms.map(({ localParticipant }) => new Promise(resolve => localParticipant.once('reconnecting', resolve))); - const reconnectingPromises = rooms.map(room => new Promise(resolve => room.once('reconnecting', resolve))); - const reconnectedPromises = rooms.map(room => new Promise(resolve => room.once('reconnected', resolve))); + // disconnect from current network(s). + await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from network'); + await readCurrentNetworks(dockerAPI); + await waitToGoOnline(); - // create and connect to new network - const newNetwork = await waitFor(dockerAPI.createNetwork(), 'create network'); - await waitFor(dockerAPI.connectToNetwork(newNetwork.Id), 'connect to network'); - await readCurrentNetworks(dockerAPI); + await Promise.all([ + waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), + waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), + waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT), + waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT) + ]); - // disconnect from current network(s). - await waitFor(currentNetworks.map(({ Id: networkId }) => dockerAPI.disconnectFromNetwork(networkId)), 'disconnect from network'); + await waitWhileNotDisconnected(disconnected, rooms.map(room => validateMediaFlow(room)), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); + }); + }); - await readCurrentNetworks(dockerAPI); - await waitToGoOnline(); + describe('RemoteParticipant reconnection events (@unstable: JSDK-2815)', () => { + it('should emit "reconnecting" and "reconnected" events on the RemoteParticipant which recovers from signaling connection disruption', async () => { + const [aliceRoom, bobRoom] = rooms; + const aliceRemote = bobRoom.participants.get(aliceRoom.localParticipant.sid); + const eventsEmitted = []; + + const eventPromises = new Promise(resolve => { + const resolveIfAllEventsFired = () => eventsEmitted.length === 8 && resolve(eventsEmitted); + aliceRoom.localParticipant.on('reconnecting', () => { + eventsEmitted.push({ event: 'LocalParticipant#reconnecting' }); + resolveIfAllEventsFired(); + }); - await Promise.all([ - waitWhileNotDisconnected(disconnected, localParticipantReconnectingPromises, `localParticipantReconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), - waitWhileNotDisconnected(disconnected, reconnectingPromises, `reconnectingPromises: ${rooms[0].sid}`, RECONNECTING_TIMEOUT), - waitWhileNotDisconnected(disconnected, localParticipantReconnectedPromises, `localParticipantReconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT), - waitWhileNotDisconnected(disconnected, reconnectedPromises, `reconnectedPromises: ${rooms[0].sid}`, RECONNECTED_TIMEOUT) - ]); + aliceRoom.localParticipant.on('reconnected', () => { + eventsEmitted.push({ event: 'LocalParticipant#reconnected' }); + resolveIfAllEventsFired(); + }); - if (identities.length > 1) { - await waitWhileNotDisconnected(disconnected, rooms.map(validateMediaFlow), `validate media flow: ${rooms[0].sid}`, VALIDATE_MEDIA_FLOW_TIMEOUT); - } - }); - }); + aliceRoom.on('reconnecting', error => { + eventsEmitted.push({ event: 'LocalRoom#reconnecting', error }); + resolveIfAllEventsFired(); + }); + + aliceRoom.on('reconnected', () => { + eventsEmitted.push({ event: 'LocalRoom#reconnected' }); + resolveIfAllEventsFired(); + }); - // eslint-disable-next-line no-warning-comments - // TODO (mmalavalli): Remove environment check once RemoteParticipant "reconnecting" - // state is available in prod version of Room Service. - (identities.length > 1 ? describe : describe.skip)('RemoteParticipant reconnection events (@unstable: JSDK-2815)', () => { - it('should emit "reconnecting" and "reconnected" events on the RemoteParticipant which recovers from signaling connection disruption', async () => { - const [aliceRoom, bobRoom] = rooms; - const aliceRemote = bobRoom.participants.get(aliceRoom.localParticipant.sid); - const eventsEmitted = []; - - const eventPromises = new Promise(resolve => { - const resolveIfAllEventsFired = () => eventsEmitted.length === 8 && resolve(eventsEmitted); - aliceRoom.localParticipant.on('reconnecting', () => { - eventsEmitted.push({ event: 'LocalParticipant#reconnecting' }); - resolveIfAllEventsFired(); - }); - - aliceRoom.localParticipant.on('reconnected', () => { - eventsEmitted.push({ event: 'LocalParticipant#reconnected' }); - resolveIfAllEventsFired(); - }); - - aliceRoom.on('reconnecting', error => { - eventsEmitted.push({ event: 'LocalRoom#reconnecting', error }); - resolveIfAllEventsFired(); - }); - - aliceRoom.on('reconnected', () => { - eventsEmitted.push({ event: 'LocalRoom#reconnected' }); - resolveIfAllEventsFired(); - }); - - aliceRemote.on('reconnecting', () => { - eventsEmitted.push({ event: 'RemoteParticipant#reconnecting' }); - resolveIfAllEventsFired(); - }); - - aliceRemote.on('reconnected', () => { - eventsEmitted.push({ event: 'RemoteParticipant#reconnected' }); - resolveIfAllEventsFired(); - }); - - bobRoom.on('participantReconnecting', participant => { - eventsEmitted.push({ event: 'RemoteRoom#participantReconnecting', participant }); - resolveIfAllEventsFired(); - }); - - bobRoom.on('participantReconnected', participant => { - eventsEmitted.push({ event: 'RemoteRoom#participantReconnected', participant }); - resolveIfAllEventsFired(); - }); + aliceRemote.on('reconnecting', () => { + eventsEmitted.push({ event: 'RemoteParticipant#reconnecting' }); + resolveIfAllEventsFired(); }); - // NOTE(mmalavalli): Simulate a signaling connection interruption by - // closing Alice's WebSocket transport. Then, wait until all the expected - // events are fired. NOTE: this does not work if connected quickly. Also this test is - // should not be in network tests. - aliceRoom._signaling._transport._twilioConnection._close({ code: 3005, reason: 'foo' }); - try { - await waitFor(eventPromises, 'waiting for event promises', 2 * ONE_MINUTE); - - assert.equal(eventsEmitted.length, 8); - eventsEmitted.forEach(item => { - switch (item.event) { - case 'LocalRoom#reconnecting': - assert(item.error instanceof SignalingConnectionDisconnectedError); - break; - case 'RemoteRoom#participantReconnecting': - case 'RemoteRoom#participantReconnected': - assert.equal(item.participant, aliceRemote); - break; - } - }); - } catch (err) { - console.log('eventsEmitted:', eventsEmitted); - throw err; - } + aliceRemote.on('reconnected', () => { + eventsEmitted.push({ event: 'RemoteParticipant#reconnected' }); + resolveIfAllEventsFired(); + }); + + bobRoom.on('participantReconnecting', participant => { + eventsEmitted.push({ event: 'RemoteRoom#participantReconnecting', participant }); + resolveIfAllEventsFired(); + }); + + bobRoom.on('participantReconnected', participant => { + eventsEmitted.push({ event: 'RemoteRoom#participantReconnected', participant }); + resolveIfAllEventsFired(); + }); }); + + // Block Alice's signaling traffic. + await dockerAPI.blockIpRanges(DOCKER_PROXY_VSG_IP_RANGES[aliceRoom.localParticipant.signalingRegion], ['tcp']); + + // Wait for Bob's Room to emit participantReconnecting event for Alice. + await waitOnceForRoomEvent(bobRoom, 'participantReconnecting'); + + // Unblock Alice's signaling traffic. + await dockerAPI.unblockIpRanges(DOCKER_PROXY_VSG_IP_RANGES[aliceRoom.localParticipant.signalingRegion], ['tcp']); + + try { + await waitFor(eventPromises, 'event promises', 2 * ONE_MINUTE); + assert.equal(eventsEmitted.length, 8); + eventsEmitted.forEach(item => { + switch (item.event) { + case 'LocalRoom#reconnecting': + assert(item.error instanceof SignalingConnectionDisconnectedError); + break; + case 'RemoteRoom#participantReconnecting': + case 'RemoteRoom#participantReconnected': + assert.equal(item.participant, aliceRemote); + break; + } + }); + } catch (err) { + console.log('events emitted:', eventsEmitted.map(({ event }) => event)); + throw err; + } }); }); }); @@ -473,6 +479,7 @@ describe('network:', function() { before(this.title, async function() { if (!isRunningInsideDocker) { this.skip(); + return; } const identities = defaults.topology === 'peer-to-peer' diff --git a/test/lib/post.js b/test/lib/post.js index 00a81704f..bd3147dd4 100644 --- a/test/lib/post.js +++ b/test/lib/post.js @@ -4,11 +4,6 @@ const https = require('https'); const { apiKeySecret, apiKeySid } = require('../env'); const { environment } = require('../lib/defaults'); -const { version } = require('../../package.json'); - -const HOST_NAME_ECS = environment === 'prod' - ? 'ecs.us1.twilio.com' - : `ecs.${environment}-us1.twilio.com`; const HOST_NAME_REST = environment === 'prod' ? 'video.twilio.com' @@ -66,26 +61,6 @@ function post(config, data) { }, config), data); } -/** - * Make an ECS request. - * @param {string} token - * @returns {Promise<*>} - */ -function postECS(token) { - return post({ - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-Twilio-Token': token, - }, - hostname: HOST_NAME_ECS, - path: '/v2/Configuration' - }, { - service: 'video', - // eslint-disable-next-line camelcase - sdk_version: version - }); -} - /** * Make an REST request. * @param {string} path @@ -119,7 +94,6 @@ function getREST(path) { }); } -exports.ecs = postECS; exports.rest = postREST; exports.getREST = getREST; diff --git a/test/lib/util.js b/test/lib/util.js index 188381e00..b3e25be5f 100644 --- a/test/lib/util.js +++ b/test/lib/util.js @@ -10,7 +10,6 @@ const { capitalize } = require('../../lib/util'); const { isSafari } = require('./guessbrowser'); const defaults = require('../lib/defaults'); const getToken = require('../lib/token'); -const { ecs } = require('../lib/post'); const { createRoom } = require('../lib/rest'); const connect = require('../../lib/connect'); const second = 1000; @@ -679,23 +678,6 @@ function waitForEvent(emitter, event) { return new Promise(resolve => emitter.once(event, resolve)); } -/** - * Get the regionalized RTCIceServers[]. - * @param {string} token - * @param {string} region - * @returns {Promise} - */ -async function getRegionalizedIceServers(token, region) { - const config = await ecs(token); - const videoConfig = config.video || {}; - const nts = videoConfig.network_traversal_service || {}; - const iceServers = nts.ice_servers || []; - iceServers.forEach(iceServer => { - iceServer.urls = iceServer.urls.replace(/global/, region); - }); - return iceServers; -} - function getTotalBytesReceived(statReports, trackTypes = ['remoteVideoTrackStats', 'remoteAudioTrackStats']) { let totalBytesReceived = 0; statReports.forEach(statReport => { @@ -744,7 +726,6 @@ exports.combinationContext = combinationContext; exports.combinations = combinations; exports.isRTCRtpSenderParamsSupported = isRTCRtpSenderParamsSupported; exports.dominantSpeakerChanged = dominantSpeakerChanged; -exports.getRegionalizedIceServers = getRegionalizedIceServers; exports.makeEncodingParameters = makeEncodingParameters; exports.pairs = pairs; exports.participantsConnected = participantsConnected;