diff --git a/src/lib/map_as3.js b/src/lib/map_as3.js index 4d386e1..6df2965 100644 --- a/src/lib/map_as3.js +++ b/src/lib/map_as3.js @@ -2308,6 +2308,7 @@ const translate = { if (itemCopy.serverScope === undefined) { itemCopy.serverScope = 'any'; } + itemCopy.autoDelete = (itemCopy.autoDelete === undefined) ? 'true' : itemCopy.autoDelete; const taggedId = `Service_Address-${itemId}`; return { configs: [normalize.actionableMcp(context, itemCopy, 'ltm virtual-address', util.mcpPath(tenantId, newAppId, taggedId))] }; }, diff --git a/src/lib/postValidator.js b/src/lib/postValidator.js index f098728..59a7962 100644 --- a/src/lib/postValidator.js +++ b/src/lib/postValidator.js @@ -41,13 +41,27 @@ class postValidator { return promise.then(() => tcpProfile(context, declaration)) .then(() => sslProfile(context, declaration)) .then(() => protocolInspectionProfile(context, declaration)) - .then(() => service(context, declaration)); + .then(() => service(context, declaration)) + .then(() => virtualAddressInspect(declaration)); } return promise; } } +function virtualAddressInspect(declaration) { + const virtualAddressProfiles = findVirtualAddressinTenants(declaration, 'Service_Address'); + let err; + + if (virtualAddressProfiles.some((profile) => profile.autoDelete === false)) { + err = new Error('Disabling auto-delete for service addresses in non-common tenants is not supported.'); + err.statusCode = 422; + return Promise.reject(err); + } + + return Promise.resolve(); +} + function protocolInspectionProfile(context, declaration) { const profiles = findItems(declaration, 'Protocol_Inspection_Profile'); let err; @@ -150,6 +164,28 @@ function findItems(declaration, itemName) { return items; } +function findVirtualAddressinTenants(declaration, itemName) { + const items = []; + const decKeys = Object.keys(declaration); + decKeys.forEach((decKey) => { + const tenant = declaration[decKey]; + if (declarationUtil.isTenant(tenant) && decKey !== 'Common') { + Object.keys(tenant).forEach((tenKey) => { + const application = tenant[tenKey]; + if (declarationUtil.isApplication(application)) { + Object.keys(application).forEach((appKey) => { + const item = application[appKey]; + if (typeof item === 'object' && item.class === itemName) { + items.push(item); + } + }); + } + }); + } + }); + return items; +} + function validatePathLengths(declaration) { let err; const decKeys = Object.keys(declaration); diff --git a/src/lib/properties.json b/src/lib/properties.json index 5309e10..7ed50bb 100644 --- a/src/lib/properties.json +++ b/src/lib/properties.json @@ -1303,7 +1303,8 @@ { "id":"route-advertisement" }, { "id":"spanning", "truth": "enabled", "falsehood": "disabled", "altId": "spanningEnabled" }, { "id":"server-scope" }, - { "id":"traffic-group" } + { "id":"traffic-group" }, + { "id":"auto-delete" , "truth": "true", "falsehood": "false" } ], "ltm cipher rule": [ { "id":"cipher" }, diff --git a/src/schema/latest/core-schema.json b/src/schema/latest/core-schema.json index a9e82af..53c7a94 100644 --- a/src/schema/latest/core-schema.json +++ b/src/schema/latest/core-schema.json @@ -2618,6 +2618,12 @@ "description": "Specifies the traffic group which the Service_Address belongs.", "type": "string", "default": "default" + }, + "autoDelete": { + "title": "Auto Delete", + "description": "If this is true, MCPD deletes the virtual address if it is not linked to any virtual. This option applies only to the Common Tenant.", + "type": "boolean", + "default": true } }, "dependencies": { diff --git a/test/integration/bigip/misc/serviceAddress.js b/test/integration/bigip/misc/serviceAddress.js index 39f9575..2a207fd 100644 --- a/test/integration/bigip/misc/serviceAddress.js +++ b/test/integration/bigip/misc/serviceAddress.js @@ -412,6 +412,55 @@ describe('serviceAddress', function () { .then(() => deleteBigipItems(bigipItems))); }); + it('should not be thrown an error if the Service Address autoDelete field is enabled on non-common partitions', () => { + const decl = { + class: 'ADC', + schemaVersion: '3.0.0', + tenant: { + class: 'Tenant', + app: { + class: 'Application', + vaddr: { + class: 'Service_Address', + virtualAddress: '10.0.1.2', + autoDelete: true + } + } + } + }; + + return Promise.resolve() + .then(() => postDeclaration(decl, { declarationIndex: 0 })) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }).finally(() => deleteDeclaration()); + }); + + it('should be thrown an error if the Service Address autoDelete field is disabled on non-common partitions.', () => { + const decl = { + class: 'ADC', + schemaVersion: '3.0.0', + tenant: { + class: 'Tenant', + app: { + class: 'Application', + vaddr: { + class: 'Service_Address', + virtualAddress: '10.0.1.2', + autoDelete: false + } + } + } + }; + + return Promise.resolve() + .then(() => postDeclaration(decl, { declarationIndex: 0 })) + .then((response) => { + assert.strictEqual(response.results[0].code, 422); + assert.strictEqual(response.results[0].message, 'Disabling auto-delete for service addresses in non-common tenants is not supported.'); + }); + }); + describe('per-app', () => { let Shared; let serviceApp; @@ -483,6 +532,56 @@ describe('serviceAddress', function () { 'virtual-address should have been deleted' )); }); + + it('should delete the Service_Address in Common partition using autoDelete flag', () => { + const perAppCommonPath = '/mgmt/shared/appsvcs/declare/Common/applications'; + const perAppTenantPath = '/mgmt/shared/appsvcs/declare/Tenant/applications'; + Shared.ServiceAddress.autoDelete = true; + serviceApp.Service.virtualAddresses.push({ use: '/Common/Shared/ServiceAddress' }); + return Promise.resolve() + .then(() => postDeclaration({ schemaVersion: '3.50', Shared }, { declarationIndex: 0 }, undefined, perAppCommonPath)) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + .then(() => getPath('/mgmt/tm/ltm/virtual-address/~Common~Shared~ServiceAddress')) + .then(() => postDeclaration({ schemaVersion: '3.50', serviceApp }, { declarationIndex: 1 }, undefined, perAppTenantPath)) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + // Virtual-address should be removed from /Common/Shared once associated app is removed + .then(() => deleteDeclaration(undefined, { path: `${perAppTenantPath}/serviceApp?async=true`, sendDelete: true })) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + .then(() => assert.isRejected( + getPath('/mgmt/tm/ltm/virtual-address/~Common~Shared~ServiceAddress'), + /The requested Virtual Address \(\/Common\/Shared\/ServiceAddress\) was not found/, + 'virtual-address should have been deleted' + )); + }); + + it('should not delete the Service_Address in Common partition using autoDelete flag', () => { + const perAppCommonPath = '/mgmt/shared/appsvcs/declare/Common/applications'; + const perAppTenantPath = '/mgmt/shared/appsvcs/declare/Tenant/applications'; + Shared.ServiceAddress.autoDelete = false; + serviceApp.Service.virtualAddresses.push({ use: '/Common/Shared/ServiceAddress' }); + return Promise.resolve() + .then(() => postDeclaration({ schemaVersion: '3.50', Shared }, { declarationIndex: 0 }, undefined, perAppCommonPath)) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + .then(() => getPath('/mgmt/tm/ltm/virtual-address/~Common~Shared~ServiceAddress')) + .then(() => postDeclaration({ schemaVersion: '3.50', serviceApp }, { declarationIndex: 1 }, undefined, perAppTenantPath)) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + // Virtual-address should not be removed from /Common/Shared once associated app is removed + .then(() => deleteDeclaration(undefined, { path: `${perAppTenantPath}/serviceApp?async=true`, sendDelete: true })) + .then((response) => { + assert.strictEqual(response.results[0].code, 200); + }) + .then(() => getPath('/mgmt/tm/ltm/virtual-address/~Common~Shared~ServiceAddress')); + }); }); describe('virtualAddress with RouteDomain ID', () => { diff --git a/test/unit/lib/map_as3.js b/test/unit/lib/map_as3.js index 4a41753..29673c8 100644 --- a/test/unit/lib/map_as3.js +++ b/test/unit/lib/map_as3.js @@ -2758,6 +2758,53 @@ describe('map_as3', () => { const results = translate.Service_Address(defaultContext, 'foo', 'bar', '10.10.0.11', item, declaration); assert.strictEqual(results.configs[0].path, '/foo/Service_Address-10.10.0.11'); }); + + it('should check autoDelete flag is true by detaul in Service Address', () => { + const item = { + arp: true, + icmpEcho: 'enable', + spanning: false, + shareAddresses: false, + virtualAddress: '10.10.0.12', + serverScope: 'all' + }; + + const results = translate.Service_Address(defaultContext, 'foo', 'bar', '10.10.0.12', item, declaration); + assert.strictEqual(results.configs[0].path, '/foo/Service_Address-10.10.0.12'); + assert.strictEqual(results.configs[0].properties['auto-delete'], 'true'); + }); + + it('should check autoDelete flag value is true in Service Address', () => { + const item = { + arp: true, + icmpEcho: 'enable', + spanning: false, + shareAddresses: false, + virtualAddress: '10.10.0.12', + serverScope: 'all', + autoDelete: true + }; + + const results = translate.Service_Address(defaultContext, 'foo', 'bar', '10.10.0.12', item, declaration); + assert.strictEqual(results.configs[0].path, '/foo/Service_Address-10.10.0.12'); + assert.strictEqual(results.configs[0].properties['auto-delete'], 'true'); + }); + + it('should check autoDelete flag value is false in Service Address', () => { + const item = { + arp: true, + icmpEcho: 'enable', + spanning: false, + shareAddresses: false, + virtualAddress: '10.10.0.12', + serverScope: 'all', + autoDelete: false + }; + + const results = translate.Service_Address(defaultContext, 'foo', 'bar', '10.10.0.12', item, declaration); + assert.strictEqual(results.configs[0].path, '/foo/Service_Address-10.10.0.12'); + assert.strictEqual(results.configs[0].properties['auto-delete'], 'false'); + }); }); }); @@ -6163,7 +6210,8 @@ describe('map_as3', () => { 'route-advertisement': 'disabled', spanning: 'disabled', 'traffic-group': 'default', - 'server-scope': 'any' + 'server-scope': 'any', + 'auto-delete': 'true' }, ignore: [] }, @@ -12786,7 +12834,8 @@ describe('map_as3', () => { 'route-advertisement': 'disabled', spanning: 'disabled', 'traffic-group': 'default', - 'server-scope': 'any' + 'server-scope': 'any', + 'auto-delete': 'true' }; const result = translate.Service_Forwarding(defaultContext, 'tenantId', 'appId', 'itemId', item); diff --git a/test/unit/lib/postValidator.js b/test/unit/lib/postValidator.js index 0d11880..cd57cc7 100644 --- a/test/unit/lib/postValidator.js +++ b/test/unit/lib/postValidator.js @@ -426,5 +426,70 @@ describe('postValidator', () => { ); }); }); + + describe('AutoDelete', () => { + const declaration = { + class: 'ADC', + schemaVersion: '3.50.0', + Common: { + class: 'Tenant', + Shared: { + class: 'Application', + template: 'shared', + 'x_service-address_any': { + class: 'Service_Address', + virtualAddress: '0.0.0.0', + icmpEcho: 'disable', + arpEnabled: false, + autoDelete: true + } + } + } + }; + + const declaration1 = { + class: 'ADC', + schemaVersion: '3.50.0', + testTenant: { + class: 'Tenant', + testApp: { + class: 'Application', + template: 'shared', + testObj: { + class: 'Service_Address', + virtualAddress: '0.0.0.0', + icmpEcho: 'disable', + arpEnabled: false, + autoDelete: true + } + } + } + }; + + it('should be valid true for Common partion', () => { + defaultContext.target.tmosVersion = '16.1'; + return assert.isFulfilled(PostValidator.validate(defaultContext, declaration)); + }); + + it('should be valid false for Common partion', () => { + defaultContext.target.tmosVersion = '16.1'; + declaration.Common.Shared['x_service-address_any'].autoDelete = false; + return assert.isFulfilled(PostValidator.validate(defaultContext, declaration)); + }); + + it('should be valid true for non-Common partion', () => { + defaultContext.target.tmosVersion = '16.1'; + return assert.isFulfilled(PostValidator.validate(defaultContext, declaration1)); + }); + + it('should be invalid false for non-Common partion', () => { + defaultContext.target.tmosVersion = '16.1'; + declaration1.testTenant.testApp.testObj.autoDelete = false; + return assert.isRejected( + PostValidator.validate(defaultContext, declaration1), + 'Disabling auto-delete for service addresses in non-common tenants is not supported.' + ); + }); + }); }); });