From 57e44972ab82fcd166eb6c0a3a948bd2c4287661 Mon Sep 17 00:00:00 2001 From: Ittay Stern Date: Sat, 7 Dec 2019 20:05:38 +0200 Subject: Change ServiceInstance's top-level rollbackOnFailure serialization to String This will satisfy a new test: Template Topology API test: Deploy Cypress -> getTemplateTopology returns the same template Also updating templates__instance_template.json with actual "templateTopology" endpoint fields. Issue-ID: VID-724 Change-Id: I1160656c9a58ab2678ca6f2529688463fbd60a91 Signed-off-by: Ittay Stern --- .../serviceInstantiation/ServiceInstantiation.java | 20 ++- .../utils/jackson/BooleanAsStringSerializer.java | 39 ++++++ .../vid/api/InstantiationTemplatesApiTest.java | 138 +++++++++++++++++++++ .../ServiceTreeForRetry_serviceInstance.json | 2 +- .../ServiceWithFailedServiceInstance.json | 2 +- .../templates__instance_template.json | 125 +++++++++++++++++++ .../iFrames/instantiation-templates.e2e.ts | 4 +- .../templates__instance_template.json | 117 ----------------- 8 files changed, 320 insertions(+), 127 deletions(-) create mode 100644 vid-app-common/src/main/java/org/onap/vid/utils/jackson/BooleanAsStringSerializer.java create mode 100644 vid-automation/src/test/java/org/onap/vid/api/InstantiationTemplatesApiTest.java create mode 100644 vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json delete mode 100644 vid-webpack-master/cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json diff --git a/vid-app-common/src/main/java/org/onap/vid/model/serviceInstantiation/ServiceInstantiation.java b/vid-app-common/src/main/java/org/onap/vid/model/serviceInstantiation/ServiceInstantiation.java index 8828faf1d..e7e5783c4 100644 --- a/vid-app-common/src/main/java/org/onap/vid/model/serviceInstantiation/ServiceInstantiation.java +++ b/vid-app-common/src/main/java/org/onap/vid/model/serviceInstantiation/ServiceInstantiation.java @@ -21,18 +21,19 @@ package org.onap.vid.model.serviceInstantiation; import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.commons.lang3.ObjectUtils; -import org.onap.vid.job.JobAdapter; -import org.onap.vid.job.JobType; -import org.onap.vid.model.VidNotions; -import org.onap.vid.mso.model.ModelInfo; - +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.ObjectUtils; +import org.onap.vid.job.JobAdapter; +import org.onap.vid.job.JobType; +import org.onap.vid.model.VidNotions; +import org.onap.vid.mso.model.ModelInfo; +import org.onap.vid.utils.jackson.BooleanAsStringSerializer; public class ServiceInstantiation extends BaseResource implements JobAdapter.AsyncJobRequest { @@ -127,6 +128,13 @@ public class ServiceInstantiation extends BaseResource implements JobAdapter.Asy this.vidNotions = vidNotions; } + @Override + @JsonSerialize(using=BooleanAsStringSerializer.class) + public boolean isRollbackOnFailure() { + // this override is for the BooleanAsStringSerializer annotation, + // but for Service-Instance level only + return super.isRollbackOnFailure(); + } public String getOwningEntityId() { return owningEntityId; diff --git a/vid-app-common/src/main/java/org/onap/vid/utils/jackson/BooleanAsStringSerializer.java b/vid-app-common/src/main/java/org/onap/vid/utils/jackson/BooleanAsStringSerializer.java new file mode 100644 index 000000000..a61044294 --- /dev/null +++ b/vid-app-common/src/main/java/org/onap/vid/utils/jackson/BooleanAsStringSerializer.java @@ -0,0 +1,39 @@ +/*- + * ============LICENSE_START======================================================= + * VID + * ================================================================================ + * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.vid.utils.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +public class BooleanAsStringSerializer extends JsonSerializer { + + /** + * Annotate a Jackson getter with @JsonSerialize(using=BooleanAsStringSerializer.class) + * to get a boolean value be serialized as quoted string. + */ + @Override + public void serialize(Boolean bool, JsonGenerator generator, SerializerProvider provider) throws IOException { + // bool is guaranteed not to be null + generator.writeString(bool.toString()); + } +} diff --git a/vid-automation/src/test/java/org/onap/vid/api/InstantiationTemplatesApiTest.java b/vid-automation/src/test/java/org/onap/vid/api/InstantiationTemplatesApiTest.java new file mode 100644 index 000000000..a14e81f44 --- /dev/null +++ b/vid-automation/src/test/java/org/onap/vid/api/InstantiationTemplatesApiTest.java @@ -0,0 +1,138 @@ +package org.onap.vid.api; + +import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.greaterThan; +import static org.onap.vid.api.TestUtils.convertRequest; +import static vid.automation.test.services.SimulatorApi.registerExpectationFromPreset; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.onap.sdc.ci.tests.datatypes.UserCredentials; +import org.onap.simulator.presetGenerator.presets.aai.PresetAAIGetSubscribersGet; +import org.onap.vid.model.mso.MsoResponseWrapper2; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import vid.automation.test.Constants; +import vid.automation.test.model.User; +import vid.automation.test.services.AsyncJobsService; +import vid.automation.test.services.SimulatorApi.RegistrationStrategy; + +public class InstantiationTemplatesApiTest extends AsyncInstantiationBase { + + /* + Testing the Template Topology API should be very thin, given the following + assumptions: + + - Template topology API is relying on Retry's logic. + + - The templates themselves are an actual representation of the initial + state in VID's backend. This is all the knowledge that used to create + a service in the first time. So if API is fed with same state, it already + should be able to reiterate another instance. + + + The tests below will verify that: + + - A request resulting from Cypress test on "instantiation-templates.e2e.ts" + is accepted by API endpoint + + - A valid "regular" (not from template) request, yields a template that a + Cypress is able to deploy. + + These two tests are, technically, cyclic. + + Currently the only test below is shortcutting the both tests, by checking + that feeding a Cypress input yields a Template that is the same. This is + not perfect, but currently what we have. + + */ + + @Override + public UserCredentials getUserCredentials() { + User user = usersService.getUser(Constants.Users.EMANUEL_EMANUEL); + return new UserCredentials(user.credentials.userId, user.credentials.password, Constants.Users.EMANUEL_EMANUEL, "", ""); + } + + @AfterMethod + protected void dropAllFromNameCounter() { + AsyncJobsService asyncJobsService = new AsyncJobsService(); + asyncJobsService.muteAllAsyncJobs(); + asyncJobsService.dropAllFromNameCounter(); + } + + protected String templateTopologyUri(String jobId) { + return uri.toASCIIString() + "/asyncInstantiation/templateTopology/" + jobId; + } + + @Test(groups = "underDevelopment") + public void templateTopology_givenDeployFromCypressE2E_getTemplateTopologyDataIsEquivalent() throws IOException { + templateTopology_givenDeploy_templateTopologyIsEquivalent(objectMapper.readValue( + convertRequest(objectMapper, "asyncInstantiation/templates__instance_template.json"), + JsonNode.class)); + } + + public void templateTopology_givenDeploy_templateTopologyIsEquivalent(JsonNode body) { + registerExpectationFromPreset(new PresetAAIGetSubscribersGet(), RegistrationStrategy.CLEAR_THEN_SET); + + String uuid1 = postAsyncInstanceRequest(body); + JsonNode templateTopology1 = restTemplate.getForObject(templateTopologyUri(uuid1), JsonNode.class); + + assertThat(cleanupTemplate(templateTopology1), jsonEquals(cleanupTemplate(body))); + } + + private JsonNode cleanupTemplate(JsonNode templateTopology) { + return Stream.of(templateTopology) + .map(this::removeTrackById) + .map(this::removeNullValues) + .findAny().get(); + } + + private JsonNode removeTrackById(JsonNode node) { + return removeAny(node, it -> it.getKey().equals("trackById")); + } + + private JsonNode removeNullValues(JsonNode node) { + return removeAny(node, it -> it.getValue().isNull()); + } + + private JsonNode removeAny(JsonNode node, Predicate> entryPredicate) { + if (node.isObject()) { + ((ObjectNode) node).remove( + Streams.fromIterator(node.fields()) + .filter(entryPredicate) + .map(Entry::getKey) + .collect(Collectors.toList()) + ); + + for (JsonNode child : node) { + removeAny(child, entryPredicate); + } + } + + return node; + } + + private String postAsyncInstanceRequest(T body) { + String[] jobsUuids = (String[]) restTemplate.exchange( + getCreateBulkUri(), + HttpMethod.POST, + new HttpEntity<>(body), + new ParameterizedTypeReference>() { + }) + .getBody().getEntity(); + + assertThat(jobsUuids, arrayWithSize(greaterThan(0))); + return jobsUuids[0]; + } + +} diff --git a/vid-automation/src/test/resources/asyncInstantiation/ServiceTreeForRetry_serviceInstance.json b/vid-automation/src/test/resources/asyncInstantiation/ServiceTreeForRetry_serviceInstance.json index 5139aa0c0..d5b282e89 100644 --- a/vid-automation/src/test/resources/asyncInstantiation/ServiceTreeForRetry_serviceInstance.json +++ b/vid-automation/src/test/resources/asyncInstantiation/ServiceTreeForRetry_serviceInstance.json @@ -55,7 +55,7 @@ "instanceParams": [], "pause": false, "bulkSize": 1, - "rollbackOnFailure": false, + "rollbackOnFailure": "false", "isALaCarte": true, "testApi": "VNF_API", "instanceId": "INSTANCE_ID", diff --git a/vid-automation/src/test/resources/asyncInstantiation/ServiceWithFailedServiceInstance.json b/vid-automation/src/test/resources/asyncInstantiation/ServiceWithFailedServiceInstance.json index 849cb7e43..28761f393 100644 --- a/vid-automation/src/test/resources/asyncInstantiation/ServiceWithFailedServiceInstance.json +++ b/vid-automation/src/test/resources/asyncInstantiation/ServiceWithFailedServiceInstance.json @@ -27,7 +27,7 @@ "pause": false, "productFamilyId": "ddf9cc0f-6331-4d35-bed0-a37f2d5e9cb3", "projectName": "zasaki", - "rollbackOnFailure": false, + "rollbackOnFailure": "false", "statusMessage": "The service instantiation is failed.", "subscriberName": "SILVIA ROBBINS", "subscriptionServiceType": "TYLER SILVIA", diff --git a/vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json b/vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json new file mode 100644 index 000000000..5de1f4792 --- /dev/null +++ b/vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json @@ -0,0 +1,125 @@ +{ + "action": "Create", + "instanceName": "vProbe_NC_Service_DG_new_SI", + "productFamilyId": null, + "lcpCloudRegionId": null, + "tenantId": null, + "modelInfo": { + "modelInvariantId": "90a32d31-8a01-4de2-a91f-7e2414d6f5aa", + "modelVersionId": "6cfeeb18-c2b0-49df-987a-da47493c8e38", + "modelName": "vProbe_NC_Service", + "modelType": "service", + "modelVersion": "1.0" + }, + "globalSubscriberId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", + "subscriptionServiceType": "Emanuel", + "owningEntityId": "10c645f5-9924-4b89-bec0-b17cf49d3cad", + "owningEntityName": "EMANUEL-CORE", + "tenantName": null, + "aicZoneId": null, + "aicZoneName": null, + "projectName": "Kennedy", + "subscriberName": "Emanuel", + "rollbackOnFailure": "true", + "isALaCarte": true, + "testApi": "GR_API", + "trackById": "36601560-f8e3-4020-bdef-3e4709c51e84", + "existingVNFCounterMap": {"024a417d-ca46-40bf-95ce-809c6a269011": 1}, + "vnfs": { + "vProbe_NC_VNF 0": { + "modelInfo": { + "modelCustomizationName": "vProbe_NC_VNF 0", + "modelCustomizationId": "024a417d-ca46-40bf-95ce-809c6a269011", + "modelInvariantId": "a6a96924-b9c5-4c85-ae18-cbfca848095e", + "modelVersionId": "21ae311e-432f-4c54-b855-446d0b8ded72", + "modelName": "vProbe_NC_VNF", + "modelType": "vnf", + "modelVersion": "1.0" + }, + "productFamilyId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", + "instanceName": "zolson57arlba007", + "action": "Create", + "platformName": "NETWORK-CLOUD", + "lcpCloudRegionId": "olson57a", + "tenantId": "dcb28ad99c7341748830c9dc307f85eb", + "instanceParams": [ + {} + ], + "rollbackOnFailure": true, + "instanceId": null, + "vfModules": { + "vprobe_nc_vnf0..VprobeNcVnf..FE_base_module..module-0": { + "vprobe_nc_vnf0..VprobeNcVnf..FE_base_module..module-0ahubg": { + "modelInfo": { + "modelCustomizationName": "VprobeNcVnf..FE_base_module..module-0", + "modelCustomizationId": "4d0818cf-eaa9-4a3f-89c2-639953089e14", + "modelInvariantId": "29b6fa3c-aeb3-4103-b3f7-6f98e097b005", + "modelVersionId": "c5b26cc1-a66f-4b69-aa23-6abc7c647c88", + "modelName": "VprobeNcVnf..FE_base_module..module-0", + "modelType": "vfModule", + "modelVersion": "1" + }, + "instanceName": "zolson57arlba007_lba_Base_01", + "action": "Create", + "lcpCloudRegionId": "olson57a", + "tenantId": "dcb28ad99c7341748830c9dc307f85eb", + "instanceParams": [ + {} + ], + "rollbackOnFailure": true, + "trackById": "ea2879a6-10bc-4697-90d7-7bc3e71da0fd", + "isFailed": false + } + }, + "vprobe_nc_vnf0..VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1": { + "vprobe_nc_vnf0..VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1yprvi": { + "modelInfo": { + "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1", + "modelCustomizationId": "9b99d340-a80b-45ef-9ff1-993fa3e4c001", + "modelInvariantId": "1bcc4824-6c1a-4b51-af7c-076b7fc14d05", + "modelVersionId": "c09e4530-8fd8-418f-9483-2f57ce927b05", + "modelName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1", + "modelType": "vfModule", + "modelVersion": "1" + }, + "instanceName": "zolson57arlba007_lba_dj_01", + "action": "Create", + "lcpCloudRegionId": "olson57a", + "tenantId": "dcb28ad99c7341748830c9dc307f85eb", + "instanceParams": [ + {} + ], + "rollbackOnFailure": true, + "trackById": "b134410e-3bc0-478e-883e-1b6bdf8a28df", + "isFailed": false, + "volumeGroupInstanceName": "zolson57arlba007_lba_dj_01_vol", + "usePreload": true + } + } + }, + "trackById": "1d2848a0-3573-4d29-b3dd-60bb263260ea", + "isFailed": false, + "statusMessage": null, + "position": null, + "lineOfBusiness": "EMANUEL-CONSUMER" + } + }, + "networks": {}, + "vrfs": {}, + "vnfGroups": {}, + "instanceParams": [ + {} + ], + "pause": false, + "bulkSize": 1, + "instanceId": null, + "isFailed": false, + "statusMessage": null, + "vidNotions": { + "instantiationUI": "anyAlacarteWhichNotExcluded", + "modelCategory": "5G Fabric Configuration", + "viewEditUI": "legacy", + "instantiationType": "ALaCarte" + }, + "position": null +} diff --git a/vid-webpack-master/cypress/integration/iFrames/instantiation-templates.e2e.ts b/vid-webpack-master/cypress/integration/iFrames/instantiation-templates.e2e.ts index 253352729..8eb70b1fe 100644 --- a/vid-webpack-master/cypress/integration/iFrames/instantiation-templates.e2e.ts +++ b/vid-webpack-master/cypress/integration/iFrames/instantiation-templates.e2e.ts @@ -30,7 +30,7 @@ describe('Drawing Board: Instantiation Templates', function () { .as('serviceModel'); cy.route(`**/asyncInstantiation/${templateTopologyEndpoint}/${templateUuid}`, - 'fixture:../support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json') + 'fixture:../../../vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json') .as('templateTopology'); // When... @@ -48,7 +48,7 @@ describe('Drawing Board: Instantiation Templates', function () { // Then... cy.wait('@expectedPostAsyncInstantiation').then(xhr => { - cy.readFile('cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json').then((expectedResult) => { + cy.readFile('../vid-automation/src/test/resources/asyncInstantiation/templates__instance_template.json').then((expectedResult) => { convertRollbackOnFailureValueFromStringToBoolean(expectedResult); cy.deepCompare(xhr.request.body, expectedResult); }); diff --git a/vid-webpack-master/cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json b/vid-webpack-master/cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json deleted file mode 100644 index 10ddc7384..000000000 --- a/vid-webpack-master/cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__instance_template.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "action": "Create", - "instanceName": "vProbe_NC_Service_DG_new_SI", - "productFamilyId": null, - "lcpCloudRegionId": null, - "tenantId": null, - "modelInfo": { - "modelInvariantId": "90a32d31-8a01-4de2-a91f-7e2414d6f5aa", - "modelVersionId": "6cfeeb18-c2b0-49df-987a-da47493c8e38", - "modelName": "vProbe_NC_Service", - "modelType": "service", - "modelVersion": "1.0" - }, - "globalSubscriberId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", - "subscriptionServiceType": "Emanuel", - "owningEntityId": "10c645f5-9924-4b89-bec0-b17cf49d3cad", - "owningEntityName": "EMANUEL-CORE", - "tenantName": null, - "aicZoneId": null, - "aicZoneName": null, - "projectName": "Kennedy", - "subscriberName": "Emanuel", - "rollbackOnFailure": "true", - "isALaCarte": true, - "testApi": "GR_API", - "trackById": "36601560-f8e3-4020-bdef-3e4709c51e84", - "existingVNFCounterMap": {"024a417d-ca46-40bf-95ce-809c6a269011": 1}, - "vnfs": { - "vProbe_NC_VNF 0": { - "modelInfo": { - "modelCustomizationName": "vProbe_NC_VNF 0", - "modelCustomizationId": "024a417d-ca46-40bf-95ce-809c6a269011", - "modelInvariantId": "a6a96924-b9c5-4c85-ae18-cbfca848095e", - "modelVersionId": "21ae311e-432f-4c54-b855-446d0b8ded72", - "modelName": "vProbe_NC_VNF", - "modelType": "vnf", - "modelVersion": "1.0" - }, - "productFamilyId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", - "instanceName": "zolson57arlba007", - "action": "Create", - "platformName": "NETWORK-CLOUD", - "lcpCloudRegionId": "olson57a", - "tenantId": "dcb28ad99c7341748830c9dc307f85eb", - "instanceParams": [ - {} - ], - "rollbackOnFailure": true, - "vfModules": { - "vprobe_nc_vnf0..VprobeNcVnf..FE_base_module..module-0": { - "vprobe_nc_vnf0..VprobeNcVnf..FE_base_module..module-0ahubg": { - "modelInfo": { - "modelCustomizationName": "VprobeNcVnf..FE_base_module..module-0", - "modelCustomizationId": "4d0818cf-eaa9-4a3f-89c2-639953089e14", - "modelInvariantId": "29b6fa3c-aeb3-4103-b3f7-6f98e097b005", - "modelVersionId": "c5b26cc1-a66f-4b69-aa23-6abc7c647c88", - "modelName": "VprobeNcVnf..FE_base_module..module-0", - "modelType": "vfModule", - "modelVersion": "1" - }, - "instanceName": "zolson57arlba007_lba_Base_01", - "action": "Create", - "lcpCloudRegionId": "olson57a", - "tenantId": "dcb28ad99c7341748830c9dc307f85eb", - "instanceParams": [ - {} - ], - "rollbackOnFailure": true, - "trackById": "ea2879a6-10bc-4697-90d7-7bc3e71da0fd" - } - }, - "vprobe_nc_vnf0..VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1": { - "vprobe_nc_vnf0..VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1yprvi": { - "modelInfo": { - "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1", - "modelCustomizationId": "9b99d340-a80b-45ef-9ff1-993fa3e4c001", - "modelInvariantId": "1bcc4824-6c1a-4b51-af7c-076b7fc14d05", - "modelVersionId": "c09e4530-8fd8-418f-9483-2f57ce927b05", - "modelName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1", - "modelType": "vfModule", - "modelVersion": "1" - }, - "instanceName": "zolson57arlba007_lba_dj_01", - "action": "Create", - "lcpCloudRegionId": "olson57a", - "tenantId": "dcb28ad99c7341748830c9dc307f85eb", - "instanceParams": [ - {} - ], - "rollbackOnFailure": true, - "trackById": "b134410e-3bc0-478e-883e-1b6bdf8a28df", - "volumeGroupInstanceName": "zolson57arlba007_lba_dj_01_vol", - "usePreload": true - } - } - }, - "trackById": "1d2848a0-3573-4d29-b3dd-60bb263260ea", - "position": null, - "lineOfBusiness": "EMANUEL-CONSUMER" - } - }, - "networks": {}, - "vrfs": {}, - "vnfGroups": {}, - "instanceParams": [ - {} - ], - "pause": false, - "bulkSize": 1, - "vidNotions": { - "instantiationUI": "anyAlacarteWhichNotExcluded", - "modelCategory": "5G Fabric Configuration", - "viewEditUI": "legacy", - "instantiationType": "ALaCarte" - }, - "position": null -} -- cgit 1.2.3-korg