@webex/plugin-meetings 3.0.0-beta.262 → 3.0.0-beta.263
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +171 -129
- package/dist/meeting/index.js.map +1 -1
- package/dist/reachability/index.js +95 -33
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/meeting/index.d.ts +2 -2
- package/dist/types/reachability/index.d.ts +18 -1
- package/package.json +19 -19
- package/src/meeting/index.ts +32 -26
- package/src/reachability/index.ts +64 -1
- package/test/unit/spec/meeting/index.js +23 -13
- package/test/unit/spec/reachability/index.ts +208 -2
|
@@ -16,9 +16,20 @@ import ReachabilityRequest from './request';
|
|
|
16
16
|
const DEFAULT_TIMEOUT = 3000;
|
|
17
17
|
const VIDEO_MESH_TIMEOUT = 1000;
|
|
18
18
|
|
|
19
|
+
export type ReachabilityMetrics = {
|
|
20
|
+
reachability_public_udp_success: number;
|
|
21
|
+
reachability_public_udp_failed: number;
|
|
22
|
+
reachability_public_tcp_success: number;
|
|
23
|
+
reachability_public_tcp_failed: number;
|
|
24
|
+
reachability_vmn_udp_success: number;
|
|
25
|
+
reachability_vmn_udp_failed: number;
|
|
26
|
+
reachability_vmn_tcp_success: number;
|
|
27
|
+
reachability_vmn_tcp_failed: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
19
30
|
// result for a specific transport protocol (like udp or tcp)
|
|
20
31
|
export type TransportResult = {
|
|
21
|
-
reachable
|
|
32
|
+
reachable?: 'true' | 'false';
|
|
22
33
|
latencyInMilliseconds?: string;
|
|
23
34
|
clientMediaIPs?: string[];
|
|
24
35
|
untested?: 'true';
|
|
@@ -138,6 +149,58 @@ export default class Reachability {
|
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Returns statistics about last reachability results. The returned value is an object
|
|
154
|
+
* with a flat list of properties so that it can be easily sent with metrics
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise} Promise with metrics values, it never rejects/throws.
|
|
157
|
+
*/
|
|
158
|
+
async getReachabilityMetrics(): Promise<ReachabilityMetrics> {
|
|
159
|
+
const stats: ReachabilityMetrics = {
|
|
160
|
+
reachability_public_udp_success: 0,
|
|
161
|
+
reachability_public_udp_failed: 0,
|
|
162
|
+
reachability_public_tcp_success: 0,
|
|
163
|
+
reachability_public_tcp_failed: 0,
|
|
164
|
+
reachability_vmn_udp_success: 0,
|
|
165
|
+
reachability_vmn_udp_failed: 0,
|
|
166
|
+
reachability_vmn_tcp_success: 0,
|
|
167
|
+
reachability_vmn_tcp_failed: 0,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const updateStats = (clusterType: 'public' | 'vmn', result: ReachabilityResult) => {
|
|
171
|
+
if (result.udp?.reachable) {
|
|
172
|
+
const outcome = result.udp.reachable === 'true' ? 'success' : 'failed';
|
|
173
|
+
stats[`reachability_${clusterType}_udp_${outcome}`] += 1;
|
|
174
|
+
}
|
|
175
|
+
if (result.tcp?.reachable) {
|
|
176
|
+
const outcome = result.tcp.reachable === 'true' ? 'success' : 'failed';
|
|
177
|
+
stats[`reachability_${clusterType}_tcp_${outcome}`] += 1;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
const resultsJson = await this.webex.boundedStorage.get(
|
|
184
|
+
REACHABILITY.namespace,
|
|
185
|
+
REACHABILITY.localStorageResult
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const internalResults: InternalReachabilityResults = JSON.parse(resultsJson);
|
|
189
|
+
|
|
190
|
+
Object.values(internalResults).forEach((result) => {
|
|
191
|
+
updateStats(result.isVideoMesh ? 'vmn' : 'public', result);
|
|
192
|
+
});
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// empty storage, that's ok
|
|
195
|
+
LoggerProxy.logger.warn(
|
|
196
|
+
'Roap:request#getReachabilityMetrics --> Error parsing reachability data: ',
|
|
197
|
+
e
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return stats;
|
|
202
|
+
}
|
|
203
|
+
|
|
141
204
|
/**
|
|
142
205
|
* Reachability results as an object in the format that backend expects
|
|
143
206
|
*
|
|
@@ -44,14 +44,12 @@ import {
|
|
|
44
44
|
StreamEventNames,
|
|
45
45
|
} from '@webex/media-helpers';
|
|
46
46
|
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
47
|
-
import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
|
|
48
47
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
49
48
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
50
49
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
51
50
|
import Members from '@webex/plugin-meetings/src/members';
|
|
52
51
|
import * as MembersImport from '@webex/plugin-meetings/src/members';
|
|
53
52
|
import Roap from '@webex/plugin-meetings/src/roap';
|
|
54
|
-
import RoapRequest from '@webex/plugin-meetings/src/roap/request';
|
|
55
53
|
import MeetingRequest from '@webex/plugin-meetings/src/meeting/request';
|
|
56
54
|
import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/request';
|
|
57
55
|
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
@@ -81,7 +79,6 @@ import {
|
|
|
81
79
|
UserNotJoinedError,
|
|
82
80
|
MeetingNotActiveError,
|
|
83
81
|
UserInLobbyError,
|
|
84
|
-
NoMediaEstablishedYetError,
|
|
85
82
|
} from '../../../../src/common/errors/webex-errors';
|
|
86
83
|
import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-error';
|
|
87
84
|
import ParameterError from '../../../../src/common/errors/parameter';
|
|
@@ -89,19 +86,14 @@ import PasswordError from '../../../../src/common/errors/password-error';
|
|
|
89
86
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
90
87
|
import PermissionError from '../../../../src/common/errors/permission';
|
|
91
88
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
92
|
-
import DefaultSDKConfig from '../../../../src/config';
|
|
93
89
|
import testUtils from '../../../utils/testUtils';
|
|
94
90
|
import {
|
|
95
91
|
MeetingInfoV2CaptchaError,
|
|
96
92
|
MeetingInfoV2PasswordError,
|
|
97
93
|
MeetingInfoV2PolicyError,
|
|
98
94
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
99
|
-
import {ANNOTATION_POLICY} from '../../../../src/annotation/constants';
|
|
100
95
|
|
|
101
96
|
|
|
102
|
-
// Non-stubbed function
|
|
103
|
-
const {getDisplayMedia} = Media;
|
|
104
|
-
|
|
105
97
|
describe('plugin-meetings', () => {
|
|
106
98
|
const logger = {
|
|
107
99
|
info: () => {},
|
|
@@ -228,6 +220,11 @@ describe('plugin-meetings', () => {
|
|
|
228
220
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
|
229
221
|
webex.internal.metrics.submitClientMetrics = sinon.stub().returns(Promise.resolve());
|
|
230
222
|
webex.meetings.uploadLogs = sinon.stub().returns(Promise.resolve());
|
|
223
|
+
webex.meetings.reachability = {
|
|
224
|
+
isAnyPublicClusterReachable: sinon.stub().resolves(true),
|
|
225
|
+
getReachabilityResults: sinon.stub().resolves(undefined),
|
|
226
|
+
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
227
|
+
};
|
|
231
228
|
webex.internal.llm.on = sinon.stub();
|
|
232
229
|
membersSpy = sinon.spy(MembersImport, 'default');
|
|
233
230
|
meetingRequestSpy = sinon.spy(MeetingRequestImport, 'default');
|
|
@@ -1170,8 +1167,15 @@ describe('plugin-meetings', () => {
|
|
|
1170
1167
|
}
|
|
1171
1168
|
});
|
|
1172
1169
|
|
|
1173
|
-
it('should reset the statsAnalyzer to null if addMedia throws an error', async () => {
|
|
1170
|
+
it('should send metrics and reset the statsAnalyzer to null if addMedia throws an error', async () => {
|
|
1174
1171
|
meeting.meetingState = 'ACTIVE';
|
|
1172
|
+
meeting.webex.meetings.reachability = {
|
|
1173
|
+
getReachabilityMetrics: sinon.stub().resolves({
|
|
1174
|
+
someReachabilityMetric1: 'some value1',
|
|
1175
|
+
someReachabilityMetric2: 'some value2'
|
|
1176
|
+
}),
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1175
1179
|
// setup the mock to return an incomplete object - this will cause addMedia to fail
|
|
1176
1180
|
// because some methods (like on() or initiateOffer()) are missing
|
|
1177
1181
|
Media.createMediaConnection = sinon.stub().returns({
|
|
@@ -1195,6 +1199,8 @@ describe('plugin-meetings', () => {
|
|
|
1195
1199
|
signalingState: 'unknown',
|
|
1196
1200
|
connectionState: 'unknown',
|
|
1197
1201
|
iceConnectionState: 'unknown',
|
|
1202
|
+
someReachabilityMetric1: 'some value1',
|
|
1203
|
+
someReachabilityMetric2: 'some value2',
|
|
1198
1204
|
});
|
|
1199
1205
|
});
|
|
1200
1206
|
|
|
@@ -1546,6 +1552,12 @@ describe('plugin-meetings', () => {
|
|
|
1546
1552
|
|
|
1547
1553
|
it('should send ADD_MEDIA_SUCCESS metrics', async () => {
|
|
1548
1554
|
meeting.meetingState = 'ACTIVE';
|
|
1555
|
+
meeting.webex.meetings.reachability = {
|
|
1556
|
+
getReachabilityMetrics: sinon.stub().resolves({
|
|
1557
|
+
someReachabilityMetric1: 'some value1',
|
|
1558
|
+
someReachabilityMetric2: 'some value2'
|
|
1559
|
+
}),
|
|
1560
|
+
};
|
|
1549
1561
|
await meeting.addMedia({
|
|
1550
1562
|
mediaSettings: {},
|
|
1551
1563
|
});
|
|
@@ -1556,6 +1568,8 @@ describe('plugin-meetings', () => {
|
|
|
1556
1568
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1557
1569
|
connectionType: 'udp',
|
|
1558
1570
|
isMultistream: false,
|
|
1571
|
+
someReachabilityMetric1: 'some value1',
|
|
1572
|
+
someReachabilityMetric2: 'some value2'
|
|
1559
1573
|
});
|
|
1560
1574
|
|
|
1561
1575
|
assert.called(webex.internal.newMetrics.submitClientEvent);
|
|
@@ -1792,10 +1806,6 @@ describe('plugin-meetings', () => {
|
|
|
1792
1806
|
meeting.setMercuryListener = sinon.stub();
|
|
1793
1807
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
1794
1808
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
1795
|
-
meeting.webex.meetings.reachability = {
|
|
1796
|
-
isAnyPublicClusterReachable: sinon.stub().resolves(true),
|
|
1797
|
-
getReachabilityResults: sinon.stub().resolves(undefined),
|
|
1798
|
-
};
|
|
1799
1809
|
meeting.roap.doTurnDiscovery = sinon
|
|
1800
1810
|
.stub()
|
|
1801
1811
|
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
|
|
@@ -328,8 +328,6 @@ describe('getReachabilityResults', () => {
|
|
|
328
328
|
|
|
329
329
|
beforeEach(() => {
|
|
330
330
|
webex = new MockWebex();
|
|
331
|
-
|
|
332
|
-
// sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
|
|
333
331
|
});
|
|
334
332
|
|
|
335
333
|
afterEach(() => {
|
|
@@ -416,3 +414,211 @@ describe('getReachabilityResults', () => {
|
|
|
416
414
|
);
|
|
417
415
|
});
|
|
418
416
|
});
|
|
417
|
+
|
|
418
|
+
describe('getReachabilityMetrics', () => {
|
|
419
|
+
let webex;
|
|
420
|
+
|
|
421
|
+
beforeEach(() => {
|
|
422
|
+
webex = new MockWebex();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
afterEach(() => {
|
|
426
|
+
sinon.restore();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const runCheck = async (mockStorage: any, expectedResult: any) => {
|
|
430
|
+
if (mockStorage) {
|
|
431
|
+
await webex.boundedStorage.put(
|
|
432
|
+
'Reachability',
|
|
433
|
+
'reachability.result',
|
|
434
|
+
JSON.stringify(mockStorage)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const reachability = new Reachability(webex);
|
|
438
|
+
const result = await reachability.getReachabilityMetrics();
|
|
439
|
+
|
|
440
|
+
assert.deepEqual(result, expectedResult);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
it('returns all zeros if reading from local storage fails', async () => {
|
|
444
|
+
sinon.stub(webex.boundedStorage, 'get').rejects(new Error('fake error'));
|
|
445
|
+
|
|
446
|
+
const reachability = new Reachability(webex);
|
|
447
|
+
|
|
448
|
+
const result = await reachability.getReachabilityMetrics();
|
|
449
|
+
|
|
450
|
+
assert.deepEqual(result, {
|
|
451
|
+
reachability_public_udp_success: 0,
|
|
452
|
+
reachability_public_udp_failed: 0,
|
|
453
|
+
reachability_public_tcp_success: 0,
|
|
454
|
+
reachability_public_tcp_failed: 0,
|
|
455
|
+
reachability_vmn_udp_success: 0,
|
|
456
|
+
reachability_vmn_udp_failed: 0,
|
|
457
|
+
reachability_vmn_tcp_success: 0,
|
|
458
|
+
reachability_vmn_tcp_failed: 0,
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('returns correct stats based on local storage results', async () => {
|
|
463
|
+
await runCheck(
|
|
464
|
+
// mock storage:
|
|
465
|
+
{
|
|
466
|
+
public1: {
|
|
467
|
+
udp: {reachable: 'true', latencyInMilliseconds: '100'},
|
|
468
|
+
tcp: {untested: 'true'},
|
|
469
|
+
xtls: {untested: 'true'},
|
|
470
|
+
},
|
|
471
|
+
vmn1: {
|
|
472
|
+
udp: {reachable: 'true', latencyInMilliseconds: '200'},
|
|
473
|
+
tcp: {reachable: 'false'},
|
|
474
|
+
xtls: {untested: 'true'},
|
|
475
|
+
isVideoMesh: true,
|
|
476
|
+
},
|
|
477
|
+
vmn2: {
|
|
478
|
+
udp: {untested: 'true'},
|
|
479
|
+
tcp: {reachable: 'true', latencyInMilliseconds: '100', clientMediaIPs: ['10.10.10.10']},
|
|
480
|
+
xtls: {untested: 'true'},
|
|
481
|
+
isVideoMesh: true,
|
|
482
|
+
someOtherField: 'any value',
|
|
483
|
+
},
|
|
484
|
+
public2: {
|
|
485
|
+
udp: {reachable: 'false', latencyInMilliseconds: '300'},
|
|
486
|
+
tcp: {reachable: 'false', untested: 'true'},
|
|
487
|
+
xtls: {untested: 'true'},
|
|
488
|
+
someOtherField: 'any value',
|
|
489
|
+
},
|
|
490
|
+
public3: {
|
|
491
|
+
udp: {reachable: 'true', latencyInMilliseconds: '400', clientMediaIPs: ['10.10.10.10']},
|
|
492
|
+
tcp: {reachable: 'true', latencyInMilliseconds: '100', clientMediaIPs: ['10.10.10.10']},
|
|
493
|
+
xtls: {untested: 'true'},
|
|
494
|
+
isVideoMesh: false,
|
|
495
|
+
someOtherField: 'any value',
|
|
496
|
+
},
|
|
497
|
+
public4: {
|
|
498
|
+
udp: {reachable: 'true', latencyInMilliseconds: '40', clientMediaIPs: ['10.10.10.11']},
|
|
499
|
+
tcp: {reachable: 'true', latencyInMilliseconds: '100', clientMediaIPs: ['10.10.10.11']},
|
|
500
|
+
xtls: {untested: 'true'},
|
|
501
|
+
isVideoMesh: false,
|
|
502
|
+
someOtherField: 'any value',
|
|
503
|
+
},
|
|
504
|
+
public5: {
|
|
505
|
+
udp: {reachable: 'false'},
|
|
506
|
+
tcp: {untested: 'true'},
|
|
507
|
+
xtls: {untested: 'true'},
|
|
508
|
+
isVideoMesh: false,
|
|
509
|
+
someOtherField: 'any value',
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
// expected result:
|
|
513
|
+
{
|
|
514
|
+
reachability_public_udp_success: 3,
|
|
515
|
+
reachability_public_udp_failed: 2,
|
|
516
|
+
reachability_public_tcp_success: 2,
|
|
517
|
+
reachability_public_tcp_failed: 1,
|
|
518
|
+
reachability_vmn_udp_success: 1,
|
|
519
|
+
reachability_vmn_udp_failed: 0,
|
|
520
|
+
reachability_vmn_tcp_success: 1,
|
|
521
|
+
reachability_vmn_tcp_failed: 1,
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('returns correct stats when only public nodes were tested', async () => {
|
|
527
|
+
await runCheck(
|
|
528
|
+
// mock storage:
|
|
529
|
+
{
|
|
530
|
+
public1: {
|
|
531
|
+
udp: {reachable: 'true', latencyInMilliseconds: '400', clientMediaIPs: ['10.10.10.10']},
|
|
532
|
+
tcp: {reachable: 'true', latencyInMilliseconds: '100', clientMediaIPs: ['10.10.10.10']},
|
|
533
|
+
xtls: {untested: 'true'},
|
|
534
|
+
isVideoMesh: false,
|
|
535
|
+
},
|
|
536
|
+
public2: {
|
|
537
|
+
udp: {reachable: 'true', latencyInMilliseconds: '100'},
|
|
538
|
+
tcp: {untested: 'true'},
|
|
539
|
+
xtls: {untested: 'true'},
|
|
540
|
+
},
|
|
541
|
+
public3: {
|
|
542
|
+
udp: {reachable: 'false', latencyInMilliseconds: '300'},
|
|
543
|
+
tcp: {reachable: 'false', untested: 'true'},
|
|
544
|
+
xtls: {untested: 'true'},
|
|
545
|
+
someOtherField: 'any value',
|
|
546
|
+
},
|
|
547
|
+
public4: {
|
|
548
|
+
udp: {untested: 'true'},
|
|
549
|
+
tcp: {reachable: 'false'},
|
|
550
|
+
xtls: {untested: 'true'},
|
|
551
|
+
isVideoMesh: false,
|
|
552
|
+
someOtherField: 'any value',
|
|
553
|
+
},
|
|
554
|
+
public5: {
|
|
555
|
+
udp: {reachable: 'true', latencyInMilliseconds: '400', clientMediaIPs: ['10.10.10.10']},
|
|
556
|
+
tcp: {untested: 'true'},
|
|
557
|
+
xtls: {untested: 'true'},
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
// expected result:
|
|
561
|
+
{
|
|
562
|
+
reachability_public_udp_success: 3,
|
|
563
|
+
reachability_public_udp_failed: 1,
|
|
564
|
+
reachability_public_tcp_success: 1,
|
|
565
|
+
reachability_public_tcp_failed: 2,
|
|
566
|
+
reachability_vmn_udp_success: 0,
|
|
567
|
+
reachability_vmn_udp_failed: 0,
|
|
568
|
+
reachability_vmn_tcp_success: 0,
|
|
569
|
+
reachability_vmn_tcp_failed: 0,
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('returns correct stats when only video mesh nodes were tested', async () => {
|
|
575
|
+
await runCheck(
|
|
576
|
+
// mock storage:
|
|
577
|
+
{
|
|
578
|
+
vmn1: {
|
|
579
|
+
udp: {reachable: 'false'},
|
|
580
|
+
tcp: {reachable: 'true', latencyInMilliseconds: '100', clientMediaIPs: ['10.10.10.10']},
|
|
581
|
+
xtls: {untested: 'true'},
|
|
582
|
+
isVideoMesh: true,
|
|
583
|
+
},
|
|
584
|
+
vmn2: {
|
|
585
|
+
udp: {reachable: 'true', latencyInMilliseconds: '200', clientMediaIPs: ['10.10.10.10']},
|
|
586
|
+
tcp: {untested: 'true'},
|
|
587
|
+
xtls: {untested: 'true'},
|
|
588
|
+
isVideoMesh: true,
|
|
589
|
+
},
|
|
590
|
+
vmn3: {
|
|
591
|
+
udp: {reachable: 'true', latencyInMilliseconds: '300', clientMediaIPs: ['10.10.10.10']},
|
|
592
|
+
tcp: {reachable: 'false', untested: 'true'},
|
|
593
|
+
xtls: {untested: 'true'},
|
|
594
|
+
isVideoMesh: true,
|
|
595
|
+
},
|
|
596
|
+
vmn4: {
|
|
597
|
+
udp: {untested: 'true'},
|
|
598
|
+
tcp: {reachable: 'false'},
|
|
599
|
+
xtls: {untested: 'true'},
|
|
600
|
+
isVideoMesh: true,
|
|
601
|
+
someOtherField: 'any value',
|
|
602
|
+
},
|
|
603
|
+
vmn5: {
|
|
604
|
+
udp: {reachable: 'true', latencyInMilliseconds: '200', clientMediaIPs: ['10.10.10.10']},
|
|
605
|
+
tcp: {reachable: 'false'},
|
|
606
|
+
xtls: {untested: 'true'},
|
|
607
|
+
isVideoMesh: true,
|
|
608
|
+
someOtherField: 'any value',
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
// expected result:
|
|
612
|
+
{
|
|
613
|
+
reachability_public_udp_success: 0,
|
|
614
|
+
reachability_public_udp_failed: 0,
|
|
615
|
+
reachability_public_tcp_success: 0,
|
|
616
|
+
reachability_public_tcp_failed: 0,
|
|
617
|
+
reachability_vmn_udp_success: 3,
|
|
618
|
+
reachability_vmn_udp_failed: 1,
|
|
619
|
+
reachability_vmn_tcp_success: 1,
|
|
620
|
+
reachability_vmn_tcp_failed: 3,
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
});
|