diff --git a/app/waf/src/services/bigip.service.ts b/app/waf/src/services/bigip.service.ts index 6eec5c08a..8660e10b2 100644 --- a/app/waf/src/services/bigip.service.ts +++ b/app/waf/src/services/bigip.service.ts @@ -100,44 +100,34 @@ export class BigIpManager { await this.mustBeReachable(); let url = `${this.baseUrl}/mgmt/tm/net/interface`; + let response = await this.bigipService.getInfo(url, this.cred64Encoded); + let resObj = JSON.parse(JSON.stringify(response))['body'][0]; + this.logger.debug(`get ${url} responses: ${JSON.stringify(resObj)}`); - let impFunc = async () => { - let response = await this.bigipService.getInfo(url, this.cred64Encoded); - let resObj = JSON.parse(JSON.stringify(response))['body'][0]; - this.logger.debug(`get ${url} responses: ${JSON.stringify(resObj)}`); - return resObj; - }; + let items = resObj['items']; + let interfaces: BigipInterfaces = {}; + for (let intf of items) { + let macAddr = intf.macAddress; + interfaces[macAddr] = { + name: intf.name, + macAddress: macAddr, + }; + } + + return interfaces; + } + // The interface mac addresses are 'none' at the very beginning of the bigip readiness. + // So we need to check and wait it becomes non-none. + async getInterfacesNoNone(): Promise { + let infs: BigipInterfaces; let checkFunc = async () => { - return await impFunc().then(resObj => { - let items = resObj['items']; - for (let intf of items) { - if (intf.macAddress === 'none') { - this.logger.warn("bigip interface's mac addr is 'none', waiting.."); - return false; - } - } - this.logger.debug('bigip mac addresses are ready to get.'); - return true; - }); + infs = await this.getInterfaces(); + return Object.keys(infs).indexOf('none') < 0; }; - // The interface mac addresses are 'none' at the very beginning of the bigip readiness. return await checkAndWait(checkFunc, 60).then( - async () => { - return await impFunc().then(resObj => { - let items = resObj['items']; - let interfaces: BigipInterfaces = {}; - for (let intf of items) { - let macAddr = intf.macAddress; - interfaces[macAddr] = { - name: intf.name, - macAddress: macAddr, - }; - } - return interfaces; - }); - }, + () => infs, () => { throw new Error('bigip mac addresses are not ready to get.'); }, @@ -176,50 +166,17 @@ export class BigIpManager { await this.mustBeReachable(); let url = `${this.baseUrl}/mgmt/tm/cm/device`; + let response = await this.bigipService.getInfo(url, this.cred64Encoded); + let resObj = JSON.parse(JSON.stringify(response))['body'][0]; + this.logger.debug(`get ${url} responses: ${JSON.stringify(resObj)}`); - let impFunc = async () => { - let response = await this.bigipService.getInfo(url, this.cred64Encoded); - let resObj = JSON.parse(JSON.stringify(response))['body'][0]; - this.logger.debug(`get ${url} responses: ${JSON.stringify(resObj)}`); - return resObj; - }; - - let checkFunc = async () => { - return await impFunc().then(resObj => { - let items = resObj['items']; - for (let item of items) { - if ( - item.managementIp === this.config.ipAddr && - item.configsyncIp !== 'none' - ) { - return true; - } else { - this.logger.warn('No configsync IP, waiting...'); - return false; - } - } - this.logger.debug('Configsync IP is ready.'); - return true; - }); - }; - - return await checkAndWait(checkFunc, 60).then( - async () => { - return await impFunc().then(resObj => { - let items = resObj['items']; - let ip = ''; - for (let item of items) { - if (item.managementIp === this.config.ipAddr) { - ip = item.configsyncIp; - } - } - return ip; - }); - }, - () => { - throw new Error('No configsync IP'); - }, - ); + let items = resObj['items']; + for (let item of items) { + if (item.managementIp === this.config.ipAddr) { + return item.configsyncIp; + } + } + throw new Error('No configsync IP'); } async getDOStatus(): Promise { @@ -231,7 +188,7 @@ export class BigIpManager { return resObj; } - async uploadDO(): Promise { + async uploadDO(): Promise { await this.mustBeReachable(); const filename = process.env.DO_RPM_PACKAGE!; let fs = require('fs'); @@ -239,25 +196,20 @@ export class BigIpManager { throw new Error(`DO RPM file doesn't exist: '${filename}'`); } let fstats = fs.statSync(filename); - try { - let url = `${ - this.baseUrl - }/mgmt/shared/file-transfer/uploads/${path.basename(filename)}`; - let buffer = fs.readFileSync(filename, {endcoding: 'utf8'}); - let response = await this.bigipService.uploadFile( - url, - this.cred64Encoded, - fstats.size - 1, - fstats.size, - buffer, - ); - let resObj = JSON.stringify(response); - return resObj; - } catch (error) { - throw new Error( - `Upload DO RPM file error with error message ${error.message}`, - ); - } + + let url = `${ + this.baseUrl + }/mgmt/shared/file-transfer/uploads/${path.basename(filename)}`; + let buffer = fs.readFileSync(filename, {endcoding: 'utf8'}); + let response = await this.bigipService.uploadFile( + url, + this.cred64Encoded, + fstats.size - 1, + fstats.size, + buffer, + ); + let resObj = JSON.parse(JSON.stringify(response)).body[0]; + return resObj; } async installDO(): Promise { @@ -268,49 +220,51 @@ export class BigIpManager { process.env.DO_RPM_PACKAGE!, )}`, }; - try { - let url = `${this.baseUrl}/mgmt/shared/iapp/package-management-tasks`; - let response = await this.bigipService.installObject( - url, + + let url = `${this.baseUrl}/mgmt/shared/iapp/package-management-tasks`; + let response = await this.bigipService.installObject( + url, + this.cred64Encoded, + body, + ); + let taskid = JSON.parse(JSON.stringify(response))['body'][0]['id']; + let dourl = `${this.baseUrl}/mgmt/shared/iapp/package-management-tasks/${taskid}`; + + let status: string; + let resChk: object; + + let checkFunc = async () => { + let checkinfo = await this.bigipService.getInfo( + dourl, this.cred64Encoded, - body, ); - let taskid = JSON.parse(JSON.stringify(response))['body'][0]['id']; - let dourl = `${this.baseUrl}/mgmt/shared/iapp/package-management-tasks/${taskid}`; + let resObj = JSON.parse(JSON.stringify(checkinfo))['body'][0]; + this.logger.debug(`get ${dourl} responses: ${JSON.stringify(resObj)}`); + status = resObj['status']; + resChk = resObj; + + if (status === 'FAILED') return Promise.reject(true); + return status === 'FINISHED'; + }; - let impFunc = async () => { - let checkinfo = await this.bigipService.getInfo( - dourl, - this.cred64Encoded, + return await checkAndWait(checkFunc, 60).then( + async () => status, + () => { + throw new Error( + `Install DO failed: (status: ${status}, detail: ${JSON.stringify( + resChk, + )})`, ); - let resObj = JSON.parse(JSON.stringify(checkinfo))['body'][0]; - this.logger.debug(`get ${url} responses: ${JSON.stringify(resObj)}`); - return resObj; - }; + }, + ); + } - let checkFunc = async () => { - return await impFunc().then(resObj => { - let status = resObj['status']; - if (status === 'FINISHED') return true; - }); - }; + async getAS3Info(): Promise { + await this.mustBeReachable(); - return await checkAndWait(checkFunc, 60).then( - async () => { - return await impFunc().then(resObj => { - let status = resObj['status']; - return status; - }); - }, - () => { - throw new Error('Install DO failed.'); - }, - ); - } catch (error) { - throw new Error( - `Install DO RPM file error with error message ${error.message}`, - ); - } + let url = `${this.baseUrl}/mgmt/shared/appsvcs/info`; + let response = await this.bigipService.getInfo(url, this.cred64Encoded); + return JSON.parse(JSON.stringify(response))['body'][0]; } async getHostname(): Promise { diff --git a/app/waf/src/services/do.service.ts b/app/waf/src/services/do.service.ts index c3641cd28..1a3f60db1 100644 --- a/app/waf/src/services/do.service.ts +++ b/app/waf/src/services/do.service.ts @@ -376,7 +376,7 @@ export class OnboardingManager { }, this.reqId, ).then(async bigipMgr => { - return await bigipMgr.getInterfaces(); + return await bigipMgr.getInterfacesNoNone(); }), subnets: await this.subnetInfo(obData, addon), onboarding: addon.onboarding === true, diff --git a/app/waf/test/fixtures/datasources/testrest.datasource.ts b/app/waf/test/fixtures/datasources/testrest.datasource.ts index d3eba440f..d0d10e29f 100644 --- a/app/waf/test/fixtures/datasources/testrest.datasource.ts +++ b/app/waf/test/fixtures/datasources/testrest.datasource.ts @@ -61,6 +61,7 @@ export const ExpectedData = { bigipMgmt: { hostname: 'test-asm.example1234.openstack.com', tcpPort: RestApplicationPort.SSLCustom, + licenseKey: 'JTSCD-MTSZS-MHVKG-YDMJY-XVSECOT', }, networks: { management: { @@ -99,6 +100,7 @@ export const ExpectedData = { memberId: '895cc33f_7af6_4477_adc4_c286908f0e72', applicationId: '1c19251d-7e97-411a-8816-6f7a72403707', trustDeviceId: '80e5aa54-ff6b-4717-9b53-6e8deafdebad', + requestId: 'f488a7a2-bd74-42cb-8d6d-6b842efe24f7', }; export const StubResponses = { @@ -150,6 +152,10 @@ export const StubResponses = { ); }, + emptyResponse: () => { + return {}; + }, + v2AuthToken200: () => { return { access: { @@ -1162,7 +1168,7 @@ export const StubResponses = { description: 'Z100', }, registrationKey: { - description: 'JTSCD-MTSZS-MHVKG-YDMJY-XVSECOT', + description: ExpectedData.bigipMgmt.licenseKey, }, serviceCheckDate: { description: '2019/05/04', @@ -1176,12 +1182,97 @@ export const StubResponses = { }; }, + bigipCmDeviceNone200: () => { + return { + kind: 'tm:cm:device:devicecollectionstate', + selfLink: 'https://localhost/mgmt/tm/cm/device?ver=13.1.1', + items: [ + { + kind: 'tm:cm:device:devicestate', + name: 'bigip1', + partition: 'Common', + fullPath: '/Common/bigip1', + generation: 8, + selfLink: + 'https://localhost/mgmt/tm/cm/device/~Common~bigip1?ver=13.1.1', + baseMac: 'fa:16:3e:2d:e4:dd', + build: '0.0.4', + cert: '/Common/dtdi.crt', + certReference: { + link: + 'https://localhost/mgmt/tm/cm/cert/~Common~dtdi.crt?ver=13.1.1', + }, + chassisId: 'c12d30e9-dd5b-b744-4b2259ca2808', + chassisType: 'individual', + configsyncIp: 'none', + edition: 'Final', + failoverState: 'active', + haCapacity: 0, + hostname: 'host-10-250-15-133.openstacklocal', + key: '/Common/dtdi.key', + keyReference: { + link: + 'https://localhost/mgmt/tm/cm/key/~Common~dtdi.key?ver=13.1.1', + }, + managementIp: ExpectedData.networks.management.ipAddr, + marketingName: 'BIG-IP Virtual Edition', + mirrorIp: 'any6', + mirrorSecondaryIp: 'any6', + multicastIp: 'any6', + multicastPort: 0, + platformId: 'Z100', + product: 'BIG-IP', + selfDevice: 'true', + timeZone: 'America/Los_Angeles', + version: '13.1.1', + }, + ], + }; + }, + bigipCmDevice200: () => { return { + kind: 'tm:cm:device:devicecollectionstate', + selfLink: 'https://localhost/mgmt/tm/cm/device?ver=13.1.1', items: [ { + kind: 'tm:cm:device:devicestate', + name: 'bigip1', + partition: 'Common', + fullPath: '/Common/bigip1', + generation: 8, + selfLink: + 'https://localhost/mgmt/tm/cm/device/~Common~bigip1?ver=13.1.1', + baseMac: 'fa:16:3e:2d:e4:dd', + build: '0.0.4', + cert: '/Common/dtdi.crt', + certReference: { + link: + 'https://localhost/mgmt/tm/cm/cert/~Common~dtdi.crt?ver=13.1.1', + }, + chassisId: 'c12d30e9-dd5b-b744-4b2259ca2808', + chassisType: 'individual', + configsyncIp: ExpectedData.networks.ha.ipAddr, + edition: 'Final', + failoverState: 'active', + haCapacity: 0, + hostname: 'host-10-250-15-133.openstacklocal', + key: '/Common/dtdi.key', + keyReference: { + link: + 'https://localhost/mgmt/tm/cm/key/~Common~dtdi.key?ver=13.1.1', + }, managementIp: ExpectedData.networks.management.ipAddr, - configsyncIp: '1.2.3.4', + marketingName: 'BIG-IP Virtual Edition', + mirrorIp: 'any6', + mirrorSecondaryIp: 'any6', + multicastIp: 'any6', + multicastPort: 0, + platformId: 'Z100', + product: 'BIG-IP', + selfDevice: 'true', + timeZone: 'America/Los_Angeles', + version: '13.1.1', }, ], }; @@ -1224,22 +1315,6 @@ export const StubResponses = { ]; }, - bigipDOInfo200: (state: string = 'OK') => { - return [ - { - id: 0, - selfLink: 'https://localhost/mgmt/shared/declarative-onboarding/info', - result: { - class: 'Result', - code: 200, - status: state, - message: '', - errors: [], - }, - }, - ]; - }, - bigipDOInfoOK200: () => { return [ { @@ -1270,7 +1345,7 @@ export const StubResponses = { }; }, - bigipDOInstall200: () => { + bigipDOInstallStatusCreated200: () => { return { packageFilePath: '/var/config/rest/downloads/abc.rpm', operation: 'INSTALL', @@ -1293,7 +1368,32 @@ export const StubResponses = { 'https://localhost/mgmt/shared/iapp/package-management-tasks/f3a521d0-b344-44b8-a8c8-757b743a2c12', }; }, - bigipDOInstallstatus200: () => { + + bigipDOInstallStatusFailed200: () => { + return { + packageFilePath: '/var/config/rest/downloads/abc.rpm', + operation: 'INSTALL', + id: 'f3a521d0-b344-44b8-a8c8-757b743a2c12', + status: 'FAILED', + userReference: { + link: 'https://localhost/mgmt/shared/authz/users/admin', + }, + identityReferences: [ + { + link: 'https://localhost/mgmt/shared/authz/users/admin', + }, + ], + ownerMachineId: 'f2176dec-24d9-4202-8803-83f8473392b1', + generation: 1, + lastUpdateMicros: 1562732534678459, + kind: + 'shared:iapp:package-management-tasks:iapppackagemanagementtaskstate', + selfLink: + 'https://localhost/mgmt/shared/iapp/package-management-tasks/f3a521d0-b344-44b8-a8c8-757b743a2c12', + }; + }, + + bigipDOInstallStatusFinished200: () => { return { packageFilePath: '/var/config/rest/downloads/abc.rpm', packageName: 'f5-declarative-onboarding-1.5.0-11.noarch', @@ -1552,6 +1652,46 @@ export const StubResponses = { }; }, + bigipNetInterfacesNone200: () => { + return { + items: [ + { + bundle: 'not-supported', + bundleSpeed: 'not-supported', + enabled: true, + flowControl: 'tx-rx', + forceGigabitFiber: 'disabled', + forwardErrorCorrection: 'not-supported', + fullPath: '1.1', + generation: 29, + ifIndex: 48, + kind: 'tm:net:interface:interfacestate', + lldpAdmin: 'txonly', + lldpTlvmap: 130943, + macAddress: 'none', + mediaActive: 'none', + mediaFixed: '10000T-FD', + mediaMax: 'auto', + mediaSfp: 'auto', + mtu: 9198, + name: '1.1', + portFwdMode: 'l3', + preferPort: 'sfp', + qinqEthertype: '0x8100', + selfLink: 'https://localhost/mgmt/tm/net/interface/1.1?ver=13.1.1', + sflow: { + pollInterval: 0, + pollIntervalGlobal: 'yes', + }, + stp: 'enabled', + stpAutoEdgePort: 'enabled', + stpEdgePort: 'true', + stpLinkType: 'auto', + }, + ], + }; + }, + bigipNetInterfaces200: () => { return { items: [ @@ -1601,7 +1741,7 @@ export const StubResponses = { kind: 'tm:net:interface:interfacestate', lldpAdmin: 'txonly', lldpTlvmap: 130943, - macAddress: 'fa:16:3e:f3:1a:b2', + macAddress: ExpectedData.networks.internal.macAddr, mediaActive: 'none', mediaFixed: '10000T-FD', mediaMax: 'auto', @@ -1634,7 +1774,7 @@ export const StubResponses = { kind: 'tm:net:interface:interfacestate', lldpAdmin: 'txonly', lldpTlvmap: 130943, - macAddress: 'fa:16:3e:35:da:15', + macAddress: ExpectedData.networks.ha.macAddr, mediaActive: 'none', mediaFixed: '10000T-FD', mediaMax: 'auto', @@ -1667,7 +1807,7 @@ export const StubResponses = { kind: 'tm:net:interface:interfacestate', lldpAdmin: 'txonly', lldpTlvmap: 130943, - macAddress: 'fa:16:3e:94:60:40', + macAddress: ExpectedData.networks.management.macAddr, mediaActive: '100TX-FD', mediaFixed: 'auto', mediaSfp: 'auto', @@ -2348,9 +2488,9 @@ export const DefaultResponseWith: TypeResponseWith = { bigip_post_mgmt_shared_file_transfer_uploads_filename: StubResponses.bigipDOUpload200, bigip_post_mgmt_shared_iapp_package_management_tasks: - StubResponses.bigipDOInstall200, + StubResponses.bigipDOInstallStatusCreated200, bigip_get_mgmt_shared_iapp_package_management_tasks_taskId: - StubResponses.bigipDOInstallstatus200, + StubResponses.bigipDOInstallStatusFinished200, asg_post_mgmt_shared_trustproxy: StubResponses.trustProxyDeploy200, asg_get_mgmt_shared_trusteddevices_deviceId: diff --git a/app/waf/test/unit/service.BigIpManager.ts b/app/waf/test/unit/service.BigIpManager.ts new file mode 100644 index 000000000..79dfab33f --- /dev/null +++ b/app/waf/test/unit/service.BigIpManager.ts @@ -0,0 +1,209 @@ +import { + setupDepApps, + setupEnvs, + teardownDepApps, + teardownEnvs, +} from '../helpers/testsetup-helper'; +import {BigIpManager, BigipBuiltInProperties} from '../../src/services'; +import { + ExpectedData, + LetResponseWith, + StubResponses, +} from '../fixtures/datasources/testrest.datasource'; +import {expect} from '@loopback/testlab'; + +import {setDefaultInterval} from '../../src/utils'; +import {stubLogger, restoreLogger} from '../helpers/logging.helpers'; + +describe('test BigIpManager', async () => { + let bigipMgr: BigIpManager; + + let createDOFile = function() { + let fs = require('fs'); + fs.writeFileSync(process.env.DO_RPM_PACKAGE!, 'abcd', { + recursive: true, + }); + }; + let removeDOFile = function() { + let fs = require('fs'); + fs.unlinkSync(process.env.DO_RPM_PACKAGE!); + }; + + before('preworks..', async () => { + stubLogger(); + await setupEnvs(); + await setupDepApps(); + setDefaultInterval(1); + }); + + beforeEach('create bigip mgr.', async () => { + LetResponseWith(); + bigipMgr = await BigIpManager.instanlize( + { + ipAddr: ExpectedData.networks.management.ipAddr, + password: 'admin', + port: ExpectedData.bigipMgmt.tcpPort, + username: BigipBuiltInProperties.admin, + }, + ExpectedData.requestId, + ); + }); + + after('preworks..', async () => { + await teardownDepApps(); + await teardownEnvs(); + restoreLogger(); + }); + + it('getSys: ok', async () => { + let response = await bigipMgr.getSys(); + expect(response).hasOwnProperty('items'); + }); + + it('getInterfaces: ok', async () => { + let response = await bigipMgr.getInterfaces(); + expect(response).hasOwnProperty(ExpectedData.networks.management.macAddr); + }); + + it('getInterfaces: none macAddr', async () => { + LetResponseWith({ + bigip_get_mgmt_tm_net_interface: StubResponses.bigipNetInterfacesNone200, + }); + + let response = await bigipMgr.getInterfaces(); + expect(response).hasOwnProperty('none'); + }); + + it('getInterfacesNoNone: none macAddr exception', async () => { + LetResponseWith({ + bigip_get_mgmt_tm_net_interface: StubResponses.bigipNetInterfacesNone200, + }); + + try { + await bigipMgr.getInterfacesNoNone(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).eql('bigip mac addresses are not ready to get.'); + } + }); + + it('getLicense: ok', async () => { + let response = await bigipMgr.getLicense(); + expect(response.registrationKey).eql(ExpectedData.bigipMgmt.licenseKey); + }); + + it('getLicense: malform response', async () => { + LetResponseWith({ + bigip_get_mgmt_tm_sys_license: StubResponses.emptyResponse, + }); + + try { + await bigipMgr.getLicense(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).startWith('License not found: from '); + } + }); + + it('getConfigsyncIp: ok', async () => { + let response = await bigipMgr.getConfigsyncIp(); + expect(response).eql(ExpectedData.networks.ha.ipAddr); + }); + + it('getConfigsyncIp: none configsyncip', async () => { + LetResponseWith({ + bigip_get_mgmt_tm_cm_device: StubResponses.bigipCmDeviceNone200, + }); + + let response = await bigipMgr.getConfigsyncIp(); + expect(response).eql('none'); + }); + + it('getConfigsyncIp: empty exception', async () => { + LetResponseWith({ + bigip_get_mgmt_tm_cm_device: () => { + return {items: []}; + }, + }); + try { + let response = await bigipMgr.getConfigsyncIp(); + expect(response).eql('none'); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).eql('No configsync IP'); + } + }); + + it('uploadDO: ok', async () => { + createDOFile(); + let response = await bigipMgr.uploadDO(); + removeDOFile(); + // TODO: It's strange that the response is string. + // expect(response).hasOwnProperty('localFilePath'); + expect(typeof response).eql('string'); + }); + + it('uploadDO: DO file not exists', async () => { + try { + await bigipMgr.uploadDO(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).startWith("DO RPM file doesn't exist: "); + } + }); + + it('installDO: ok', async () => { + let response = await bigipMgr.installDO(); + expect(response).to.eql('FINISHED'); + }); + + it('installDO: no FINISHED state returns', async () => { + LetResponseWith({ + bigip_get_mgmt_shared_iapp_package_management_tasks_taskId: + StubResponses.bigipDOInstallStatusCreated200, + }); + + try { + await bigipMgr.installDO(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).startWith('Install DO failed:'); + } + }); + + it('installDO: FAILED state', async () => { + LetResponseWith({ + bigip_get_mgmt_shared_iapp_package_management_tasks_taskId: + StubResponses.bigipDOInstallStatusFailed200, + }); + + try { + await bigipMgr.installDO(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).startWith('Install DO failed:'); + } + }); + + it('bigip not reachable', async () => { + bigipMgr = await BigIpManager.instanlize( + { + ipAddr: 'bad.ip.address.notexists', + password: 'admin', + port: ExpectedData.bigipMgmt.tcpPort, + username: BigipBuiltInProperties.admin, + timeout: 200, + }, + ExpectedData.requestId, + ); + + try { + await bigipMgr.getSys(); + expect('Called').eql('should not happen'); + } catch (error) { + expect(error.message).to.startWith( + 'Host unreachable: {"ipaddr":"bad.ip.address.notexists', + ); + } + }); +});