Fix the full rollback flow.
Issue-ID: POLICY-5194
Change-Id: Ia2f33f4a585762a06d398b8faae6bfec823f70bd
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
import java.util.LinkedHashMap;\r
import java.util.Map;\r
import java.util.UUID;\r
-import java.util.function.UnaryOperator;\r
import lombok.Data;\r
import lombok.NoArgsConstructor;\r
import lombok.NonNull;\r
@Override
public void rollbackMigration(CompositionElementDto compositionElement,
- InstanceElementDto instanceElement, int nextStage) {
- LOGGER.debug("rollback call compositionElement: {}, instanceElement: {}", compositionElement, instanceElement);
- simulatorService.rollback(instanceElement.instanceId(), instanceElement.elementId());
+ CompositionElementDto compositionElementRollback, InstanceElementDto instanceElement,
+ InstanceElementDto instanceElementRollback, int stage) {
+ LOGGER.debug("rollback call compositionElement: {}, compositionElementRollback: {}, instanceElement: {},"
+ + " instanceElementRollback: {}, stage: {}",
+ compositionElement, compositionElementRollback, instanceElement, instanceElementRollback, stage);
+
+ if (ElementState.NEW.equals(instanceElementRollback.state())) {
+ LOGGER.debug("new element scenario");
+ }
+ if (ElementState.REMOVED.equals(instanceElementRollback.state())) {
+ simulatorService.undeploy(instanceElement.instanceId(), instanceElement.elementId(),
+ instanceElement.outProperties());
+ simulatorService.delete(instanceElement.instanceId(), instanceElement.elementId());
+ } else {
+ simulatorService.rollback(instanceElementRollback.instanceId(), instanceElementRollback.elementId(), stage,
+ compositionElementRollback.inProperties(), instanceElementRollback.outProperties());
+ }
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(SimulatorService.class);
private static final String INTERNAL_STATE = "InternalState";
private static final String MIGRATION_PROPERTY = "stage";
+ private static final String ROLLBACK_PROPERTY = "rollbackStage";
private static final String PREPARE_PROPERTY = "prepareStage";
+ private static final String STAGE_MSG = "stage %d %s";
@Getter
@Setter
} else {
intermediaryApi.updateAutomationCompositionElementStage(
instanceId, elementId,
- StateChangeResult.NO_ERROR, nextStage, "stage " + stage + " Migrated");
+ StateChangeResult.NO_ERROR, nextStage, String.format(STAGE_MSG, stage, "Migrated"));
}
} else {
intermediaryApi.updateAutomationCompositionElementState(
} else {
intermediaryApi.updateAutomationCompositionElementStage(
instanceId, elementId,
- StateChangeResult.NO_ERROR, nextStage, "stage " + stage + " Prepared");
+ StateChangeResult.NO_ERROR, nextStage, String.format(STAGE_MSG, stage, "Prepared"));
}
} else {
intermediaryApi.updateAutomationCompositionElementState(instanceId, elementId,
/**
* Handle rollback of an automation composition.
*
- * @param instanceId AC instance ID
- * @param elementId AC element ID
+ * @param instanceId the instanceId
+ * @param elementId the elementId
+ * @param stage the stage
+ * @param compositionInProperties in Properties from composition definition element
+ * @param instanceOutProperties in Properties from instance element
*/
- public void rollback(UUID instanceId, UUID elementId) {
+ public void rollback(UUID instanceId, UUID elementId, int stage, Map<String, Object> compositionInProperties,
+ Map<String, Object> instanceOutProperties) {
if (isInterrupted(getConfig().getRollbackTimerMs(),
"Current Thread for rollback was Interrupted during execution {}", instanceId)) {
LOGGER.debug("Rollback interrupted");
}
if (config.isRollback()) {
- intermediaryApi.updateAutomationCompositionElementState(instanceId, elementId, DeployState.DEPLOYED, null,
- StateChangeResult.NO_ERROR, "Migration rollback done");
+ var stageSet = ParticipantUtils.findStageSetMigrate(compositionInProperties);
+ var nextStage = 1000;
+ for (var s : stageSet) {
+ if (s > stage) {
+ nextStage = Math.min(s, nextStage);
+ }
+ }
+ instanceOutProperties.putIfAbsent(ROLLBACK_PROPERTY, new ArrayList<>());
+ @SuppressWarnings("unchecked")
+ var stageList = (List<Integer>) instanceOutProperties.get(ROLLBACK_PROPERTY);
+ stageList.add(stage);
+ intermediaryApi.sendAcElementInfo(instanceId, elementId, null, null, instanceOutProperties);
+ if (nextStage == 1000) {
+ intermediaryApi.updateAutomationCompositionElementState(
+ instanceId, elementId,
+ DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Migration rollback done");
+ } else {
+ intermediaryApi.updateAutomationCompositionElementStage(
+ instanceId, elementId,
+ StateChangeResult.NO_ERROR, nextStage, String.format(STAGE_MSG, stage, "Migration rollback"));
+ }
} else {
- intermediaryApi.updateAutomationCompositionElementState(instanceId, elementId, DeployState.DEPLOYED, null,
- StateChangeResult.FAILED, "Migration rollback failed");
+ intermediaryApi.updateAutomationCompositionElementState(
+ instanceId, elementId,
+ DeployState.DEPLOYED, null, StateChangeResult.FAILED, "Migration rollback failed");
}
}
}
package org.onap.policy.clamp.acm.participant.sim.main.handler;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
import java.util.HashMap;
import java.util.List;
var simulatorService = new SimulatorService(intermediaryApi);
var acElementHandler = new AutomationCompositionElementHandler(intermediaryApi, simulatorService);
simulatorService.setConfig(config);
-
- acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, INSTANCE_ELEMENT, DeployState.DEPLOYED.ordinal());
+ var compositionElementRollback = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
+ Map.of(), Map.of());
+ var instanceElementRollback = new InstanceElementDto(
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
+ Map.of("key", "value"), new HashMap<>());
+ acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, compositionElementRollback, INSTANCE_ELEMENT,
+ instanceElementRollback, 0);
verify(intermediaryApi).updateAutomationCompositionElementState(
- INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(), DeployState.DEPLOYED,
- null, StateChangeResult.NO_ERROR, "Migration rollback done");
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(), DeployState.DEPLOYED,
+ null, StateChangeResult.NO_ERROR, "Migration rollback done");
config.setRollback(false);
- acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, INSTANCE_ELEMENT, DeployState.DEPLOYED.ordinal());
+ acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, compositionElementRollback, INSTANCE_ELEMENT,
+ instanceElementRollback, 0);
verify(intermediaryApi).updateAutomationCompositionElementState(
INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(), DeployState.DEPLOYED,
null, StateChangeResult.FAILED, "Migration rollback failed");
}
+
@Test
- void testRollbackTimeout() {
+ void testRollbackStage() {
var config = CommonTestData.createSimConfig();
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var simulatorService = mock(SimulatorService.class, withSettings().useConstructor(intermediaryApi));
+ var simulatorService = new SimulatorService(intermediaryApi);
+ var acElementHandler = new AutomationCompositionElementHandler(intermediaryApi, simulatorService);
+ simulatorService.setConfig(config);
+ var compositionElementRollback = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
+ Map.of("stage", List.of(1, 2)), Map.of());
+ var instanceElementRollback = new InstanceElementDto(INSTANCE_ELEMENT.instanceId(),
+ INSTANCE_ELEMENT.elementId(), Map.of(), new HashMap<>());
+ acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, compositionElementRollback, INSTANCE_ELEMENT,
+ instanceElementRollback, 1);
+ verify(intermediaryApi).updateAutomationCompositionElementStage(
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
+ StateChangeResult.NO_ERROR, 2, "stage 1 Migration rollback");
+ }
- when(simulatorService.getConfig()).thenReturn(config);
- when(simulatorService.isInterrupted(anyInt(), anyString(), any())).thenReturn(true);
- doCallRealMethod().when(simulatorService).rollback(INSTANCE_ELEMENT.instanceId(),
- INSTANCE_ELEMENT.elementId());
+ @Test
+ void testRollbackAdd() {
+ var config = CommonTestData.createSimConfig();
+ var intermediaryApi = mock(ParticipantIntermediaryApi.class);
+ var simulatorService = new SimulatorService(intermediaryApi);
+ var acElementHandler = new AutomationCompositionElementHandler(intermediaryApi, simulatorService);
+ simulatorService.setConfig(config);
+ var compositionElement = new CompositionElementDto(
+ UUID.randomUUID(), new ToscaConceptIdentifier(), Map.of(), Map.of(), ElementState.NOT_PRESENT);
+ var instanceElement = new InstanceElementDto(
+ UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of(), ElementState.NOT_PRESENT);
+
+ var compoElRollbackAdd = new CompositionElementDto(
+ UUID.randomUUID(), new ToscaConceptIdentifier(), Map.of(), Map.of(), ElementState.NEW);
+ var inElRollbackAdd = new InstanceElementDto(instanceElement.instanceId(), instanceElement.elementId(),
+ Map.of(), new HashMap<>(), ElementState.NEW);
+ acElementHandler
+ .rollbackMigration(compositionElement, compoElRollbackAdd, instanceElement, inElRollbackAdd, 0);
+ verify(intermediaryApi).updateAutomationCompositionElementState(
+ instanceElement.instanceId(), instanceElement.elementId(),
+ DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Migration rollback done");
+ }
+
+ @Test
+ void testRollbackRemove() {
+ var config = CommonTestData.createSimConfig();
+ var intermediaryApi = mock(ParticipantIntermediaryApi.class);
+ var simulatorService = new SimulatorService(intermediaryApi);
var acElementHandler = new AutomationCompositionElementHandler(intermediaryApi, simulatorService);
- acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, INSTANCE_ELEMENT, DeployState.DEPLOYED.ordinal());
- verify(simulatorService).rollback(INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId());
- verify(intermediaryApi, times(0)).updateAutomationCompositionElementState(
- INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(), DeployState.DEPLOYED,
- null, StateChangeResult.NO_ERROR, "Migration rollback done");
+ simulatorService.setConfig(config);
+
+ var compoElRollbackRemove = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
+ Map.of(), Map.of(), ElementState.REMOVED);
+ var inElRollbackRemove = new InstanceElementDto(
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
+ Map.of("key", "value"), Map.of(), ElementState.REMOVED);
+ acElementHandler
+ .rollbackMigration(COMPOSITION_ELEMENT, compoElRollbackRemove, INSTANCE_ELEMENT, inElRollbackRemove, 0);
+ verify(intermediaryApi).updateAutomationCompositionElementState(
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
+ DeployState.UNDEPLOYED, null, StateChangeResult.NO_ERROR, "Undeployed");
+ verify(intermediaryApi).updateAutomationCompositionElementState(
+ INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
+ DeployState.DELETED, null, StateChangeResult.NO_ERROR, "Deleted");
}
}
/**
* Handle an update on an automation composition element.
*
- * @param compositionElement the information of the Automation Composition Definition Element
- * @param compositionElementTarget the information of the Automation Composition Definition Element Target
- * @param instanceElement the information of the Automation Composition Instance Element
- * @param instanceElementMigrate the information of the Automation Composition Instance Element updated
+ * @param compositionElement the information of the Composition Definition Element
+ * @param compositionElementTarget the information of the Composition Definition Element Target
+ * @param instanceElement the information of the Instance Element
+ * @param instanceElementMigrate the information of the Instance Element updated
* @param nextStage the next stage
* @throws PfModelException from Policy framework
*/
/**
* Rollback migration changes done to a composition.
*
- * @param compositionElement the composition to roll back the changes
- * @param instanceElement instance to roll back the changes
- * @param nextStage in which stage should the instance be after the rollback
- * @throws PfModelException if anything goes wrong
+ * @param compositionElement the information of the Automation Composition Definition Element
+ * @param compositionElementRollback the information of the Automation Composition Definition Element Target
+ * @param instanceElement the information of the Automation Composition Instance Element
+ * @param instanceElementRollback the information of the Automation Composition Instance Element updated
+ * @param stage the stage
+ * @throws PfModelException from Policy framework
*/
- void rollbackMigration(CompositionElementDto compositionElement,
- InstanceElementDto instanceElement, int nextStage)
+ void rollbackMigration(CompositionElementDto compositionElement, CompositionElementDto compositionElementRollback,
+ InstanceElementDto instanceElement, InstanceElementDto instanceElementRollback, int stage)
throws PfModelException;
}
@Override
public void rollbackMigration(CompositionElementDto compositionElement,
- InstanceElementDto instanceElement, int nextStage) {
- intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
- instanceElement.elementId(), DeployState.DEPLOYED, null,
+ CompositionElementDto compositionElementRollback, InstanceElementDto instanceElement,
+ InstanceElementDto instanceElementRollback, int stage) {
+ intermediaryApi.updateAutomationCompositionElementState(instanceElementRollback.instanceId(),
+ instanceElementRollback.elementId(), DeployState.DEPLOYED, null,
StateChangeResult.NO_ERROR, "Migration rollback done");
}
}
for (var participantDeploy : updateMsg.getParticipantUpdatesList()) {
if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
- var automationComposition = cacheProvider.getAutomationComposition(
- updateMsg.getAutomationCompositionId());
+ var automationComposition =
+ cacheProvider.getAutomationComposition(updateMsg.getAutomationCompositionId());
automationComposition.setDeployState(DeployState.UPDATING);
var acCopy = new AutomationComposition(automationComposition);
updateExistingElementsOnThisParticipant(updateMsg.getAutomationCompositionId(), participantDeploy);
}
}
- private void callParticipanDeploy(UUID messageId, List<AcElementDeploy> acElementDeployList,
- Integer startPhaseMsg, UUID instanceId) {
+ private void callParticipanDeploy(UUID messageId, List<AcElementDeploy> acElementDeployList, Integer startPhaseMsg,
+ UUID instanceId) {
var automationComposition = cacheProvider.getAutomationComposition(instanceId);
automationComposition.setDeployState(DeployState.DEPLOYING);
for (var elementDeploy : acElementDeployList) {
var element = automationComposition.getElements().get(elementDeploy.getId());
- var compositionInProperties = cacheProvider
- .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
+ var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
+ element.getDefinition());
int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
if (startPhaseMsg.equals(startPhase)) {
- var compositionElement = cacheProvider.createCompositionElementDto(
- automationComposition.getCompositionId(), element, compositionInProperties);
- var instanceElement = new InstanceElementDto(instanceId, elementDeploy.getId(),
- elementDeploy.getProperties(), element.getOutProperties());
+ var compositionElement =
+ cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
+ compositionInProperties);
+ var instanceElement =
+ new InstanceElementDto(instanceId, elementDeploy.getId(), elementDeploy.getProperties(),
+ element.getOutProperties());
listener.deploy(messageId, compositionElement, instanceElement);
}
}
}
private void callParticipantUpdateProperty(UUID messageId, List<AcElementDeploy> acElements,
- AutomationComposition acCopy) {
+ AutomationComposition acCopy) {
var instanceElementDtoMap = cacheProvider.getInstanceElementDtoMap(acCopy);
- var instanceElementDtoMapUpdated = cacheProvider.getInstanceElementDtoMap(
- cacheProvider.getAutomationComposition(acCopy.getInstanceId()));
+ var instanceElementDtoMapUpdated =
+ cacheProvider.getInstanceElementDtoMap(cacheProvider.getAutomationComposition(acCopy.getInstanceId()));
var compositionElementDtoMap = cacheProvider.getCompositionElementDtoMap(acCopy);
for (var acElement : acElements) {
listener.update(messageId, compositionElementDtoMap.get(acElement.getId()),
- instanceElementDtoMap.get(acElement.getId()), instanceElementDtoMapUpdated.get(acElement.getId()));
+ instanceElementDtoMap.get(acElement.getId()), instanceElementDtoMapUpdated.get(acElement.getId()));
}
}
private void migrateExistingElementsOnThisParticipant(UUID instanceId, UUID compositionTargetId,
- ParticipantDeploy participantDeploy, int stage) {
+ ParticipantDeploy participantDeploy, int stage) {
var automationComposition = cacheProvider.getAutomationComposition(instanceId);
var acElementList = automationComposition.getElements();
for (var element : participantDeploy.getAcElementList()) {
}
}
- private List<UUID> findElementsToRemove(List<AcElementDeploy> acElementDeployList, Map<UUID,
- AutomationCompositionElement> acElementList) {
+ private List<UUID> findElementsToRemove(List<AcElementDeploy> acElementDeployList,
+ Map<UUID, AutomationCompositionElement> acElementList) {
var acElementDeploySet = acElementDeployList.stream().map(AcElementDeploy::getId).collect(Collectors.toSet());
return acElementList.keySet().stream().filter(id -> !acElementDeploySet.contains(id)).toList();
}
/**
* Method to handle when the new state from participant is UNINITIALISED state.
*
- * @param messageId the messageId
+ * @param messageId the messageId
* @param automationComposition participant response
- * @param startPhaseMsg startPhase from message
+ * @param startPhaseMsg startPhase from message
*/
private void handleUndeployState(UUID messageId, final AutomationComposition automationComposition,
Integer startPhaseMsg) {
automationComposition.setCompositionTargetId(null);
automationComposition.setDeployState(DeployState.UNDEPLOYING);
for (var element : automationComposition.getElements().values()) {
- var compositionInProperties = cacheProvider
- .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
+ var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
+ element.getDefinition());
int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
if (startPhaseMsg.equals(startPhase)) {
element.setDeployState(DeployState.UNDEPLOYING);
- var compositionElement = cacheProvider.createCompositionElementDto(
- automationComposition.getCompositionId(), element, compositionInProperties);
+ var compositionElement =
+ cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
+ compositionInProperties);
var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
element.getProperties(), element.getOutProperties());
listener.undeploy(messageId, compositionElement, instanceElement);
Integer startPhaseMsg) {
automationComposition.setDeployState(DeployState.DELETING);
for (var element : automationComposition.getElements().values()) {
- var compositionInProperties = cacheProvider
- .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
+ var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
+ element.getDefinition());
int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
if (startPhaseMsg.equals(startPhase)) {
element.setDeployState(DeployState.DELETING);
element.setSubState(SubState.NONE);
- var compositionElement = cacheProvider.createCompositionElementDto(
- automationComposition.getCompositionId(), element, compositionInProperties);
+ var compositionElement =
+ cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
+ compositionInProperties);
var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
element.getProperties(), element.getOutProperties());
listener.delete(messageId, compositionElement, instanceElement);
migrateExistingElementsOnThisParticipant(migrationMsg.getAutomationCompositionId(),
migrationMsg.getCompositionTargetId(), participantDeploy, migrationMsg.getStage());
- if (Boolean.TRUE.equals(migrationMsg.getRollback())) {
- callParticipantRollback(migrationMsg.getMessageId(), participantDeploy.getAcElementList(),
- acCopy, migrationMsg.getCompositionTargetId(), migrationMsg.getStage());
- } else {
- callParticipantMigrate(migrationMsg.getMessageId(), participantDeploy.getAcElementList(),
- acCopy, migrationMsg.getCompositionTargetId(), migrationMsg.getStage());
- }
+ callParticipantMigrate(migrationMsg.getMessageId(), participantDeploy.getAcElementList(), acCopy,
+ migrationMsg.getCompositionTargetId(), migrationMsg.getStage(),
+ Boolean.TRUE.equals(migrationMsg.getRollback()));
}
}
}
- private void callParticipantMigrate(UUID messageId, List<AcElementDeploy> acElements,
- AutomationComposition acCopy, UUID compositionTargetId, int stage) {
+ private void callParticipantMigrate(UUID messageId, List<AcElementDeploy> acElements, AutomationComposition acCopy,
+ UUID compositionTargetId, int stage, boolean rollback) {
var compositionElementMap = cacheProvider.getCompositionElementDtoMap(acCopy);
var instanceElementMap = cacheProvider.getInstanceElementDtoMap(acCopy);
var automationComposition = cacheProvider.getAutomationComposition(acCopy.getInstanceId());
- var compositionElementTargetMap = cacheProvider.getCompositionElementDtoMap(automationComposition,
- compositionTargetId);
+ var compositionElementTargetMap =
+ cacheProvider.getCompositionElementDtoMap(automationComposition, compositionTargetId);
var instanceElementMigrateMap = cacheProvider.getInstanceElementDtoMap(automationComposition);
// Call migrate for newly added and updated elements
for (var acElement : acElements) {
- var compositionInProperties = cacheProvider
- .getCommonProperties(compositionTargetId, acElement.getDefinition());
+ var compositionInProperties =
+ cacheProvider.getCommonProperties(compositionTargetId, acElement.getDefinition());
var stageSet = ParticipantUtils.findStageSetMigrate(compositionInProperties);
if (stageSet.contains(stage)) {
if (instanceElementMap.get(acElement.getId()) == null) {
var compositionElementDto =
- new CompositionElementDto(acCopy.getCompositionId(), acElement.getDefinition(),
- Map.of(), Map.of(), ElementState.NOT_PRESENT);
- var instanceElementDto = new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(),
- Map.of(), Map.of(), ElementState.NOT_PRESENT);
- var compositionElementTargetDto = CacheProvider.changeStateToNew(
- compositionElementTargetMap.get(acElement.getId()));
- var instanceElementMigrateDto = CacheProvider
- .changeStateToNew(instanceElementMigrateMap.get(acElement.getId()));
-
- listener.migrate(messageId, compositionElementDto, compositionElementTargetDto,
- instanceElementDto, instanceElementMigrateDto, stage);
+ new CompositionElementDto(acCopy.getCompositionId(), acElement.getDefinition(), Map.of(),
+ Map.of(), ElementState.NOT_PRESENT);
+ var instanceElementDto =
+ new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(), Map.of(), Map.of(),
+ ElementState.NOT_PRESENT);
+ var compositionElementTargetDto =
+ CacheProvider.changeStateToNew(compositionElementTargetMap.get(acElement.getId()));
+ var instanceElementMigrateDto =
+ CacheProvider.changeStateToNew(instanceElementMigrateMap.get(acElement.getId()));
+
+ listenerMigrate(messageId, compositionElementDto, compositionElementTargetDto, instanceElementDto,
+ instanceElementMigrateDto, stage, rollback);
} else {
- listener.migrate(messageId, compositionElementMap.get(acElement.getId()),
+ listenerMigrate(messageId, compositionElementMap.get(acElement.getId()),
compositionElementTargetMap.get(acElement.getId()),
- instanceElementMap.get(acElement.getId()), instanceElementMigrateMap
- .get(acElement.getId()), stage);
+ instanceElementMap.get(acElement.getId()), instanceElementMigrateMap.get(acElement.getId()),
+ stage, rollback);
}
}
}
List<UUID> removedElements = findElementsToRemove(acElements, acCopy.getElements());
for (var elementId : removedElements) {
var compositionDtoTarget = new CompositionElementDto(compositionTargetId,
- acCopy.getElements().get(elementId).getDefinition(),
- Map.of(), Map.of(), ElementState.REMOVED);
- var instanceDtoTarget = new InstanceElementDto(acCopy.getInstanceId(), elementId, Map.of(),
- Map.of(), ElementState.REMOVED);
- listener.migrate(messageId, compositionElementMap.get(elementId), compositionDtoTarget,
- instanceElementMap.get(elementId), instanceDtoTarget, 0);
+ acCopy.getElements().get(elementId).getDefinition(), Map.of(), Map.of(), ElementState.REMOVED);
+ var instanceDtoTarget = new InstanceElementDto(acCopy.getInstanceId(), elementId, Map.of(), Map.of(),
+ ElementState.REMOVED);
+ listenerMigrate(messageId, compositionElementMap.get(elementId), compositionDtoTarget,
+ instanceElementMap.get(elementId), instanceDtoTarget, 0, rollback);
}
}
}
- private void callParticipantRollback(UUID messageId, List<AcElementDeploy> acElements,
- AutomationComposition acCopy, UUID compositionTargetId, int stage) {
- var compositionElementMap = cacheProvider.getCompositionElementDtoMap(acCopy);
- var instanceElementMap = cacheProvider.getInstanceElementDtoMap(acCopy);
-
- for (var acElement : acElements) {
- var compositionInProperties = cacheProvider
- .getCommonProperties(compositionTargetId, acElement.getDefinition());
- var stageSet = ParticipantUtils.findStageSetMigrate(compositionInProperties);
- if (stageSet.contains(stage)) {
- if (instanceElementMap.get(acElement.getId()) == null) {
- var compositionElementDto =
- new CompositionElementDto(acCopy.getCompositionId(), acElement.getDefinition(),
- Map.of(), Map.of(), ElementState.NOT_PRESENT);
- var instanceElementDto = new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(),
- Map.of(), Map.of(), ElementState.NOT_PRESENT);
-
- listener.rollback(messageId, compositionElementDto,
- instanceElementDto, stage);
- } else {
- listener.rollback(messageId, compositionElementMap.get(acElement.getId()),
- instanceElementMap.get(acElement.getId()), stage);
- }
- }
- }
- if (stage == 0) {
- // Call rollback for removed elements
- List<UUID> removedElements = findElementsToRemove(acElements, acCopy.getElements());
- for (var elementId : removedElements) {
- listener.rollback(messageId, compositionElementMap.get(elementId),
- instanceElementMap.get(elementId), 0);
- }
+ private void listenerMigrate(UUID messageId, CompositionElementDto compositionElement,
+ CompositionElementDto compositionElementTarget, InstanceElementDto instanceElement,
+ InstanceElementDto instanceElementMigrate, int stage, boolean rollback) {
+ if (rollback) {
+ listener.rollback(messageId, compositionElement, compositionElementTarget, instanceElement,
+ instanceElementMigrate, stage);
+ } else {
+ listener.migrate(messageId, compositionElement, compositionElementTarget, instanceElement,
+ instanceElementMigrate, stage);
}
}
}
/**
* Handles AutomationComposition Rollback.
*
- * @param messageId the messageId
* @param compositionElement the information of the Automation Composition Definition Element
+ * @param compositionElementRollback the information of the Automation Composition Definition Element Target
* @param instanceElement the information of the Automation Composition Instance Element
+ * @param instanceElementRollback the information of the Automation Composition Instance Element updated
* @param stage the stage
*/
public void rollback(UUID messageId, CompositionElementDto compositionElement,
- InstanceElementDto instanceElement,
- int stage) {
+ CompositionElementDto compositionElementRollback, InstanceElementDto instanceElement,
+ InstanceElementDto instanceElementRollback, int stage) {
cleanExecution(instanceElement.elementId(), messageId);
var result = executor.submit(() ->
- this.rollbackProcess(compositionElement,
- instanceElement, stage));
+ this.rollbackProcess(compositionElement, compositionElementRollback, instanceElement,
+ instanceElementRollback, stage));
executionMap.put(instanceElement.elementId(), result);
}
- private void rollbackProcess(CompositionElementDto compositionElement, InstanceElementDto instanceElement,
- int stage) {
+ private void rollbackProcess(CompositionElementDto compositionElement,
+ CompositionElementDto compositionElementRollback, InstanceElementDto instanceElement,
+ InstanceElementDto instanceElementRollback, int stage) {
try {
- listener.rollbackMigration(compositionElement, instanceElement, stage);
+ listener.rollbackMigration(compositionElement, compositionElementRollback, instanceElement,
+ instanceElementRollback, stage);
} catch (PfModelException e) {
LOGGER.error("Automation composition element rollback failed {} {}",
instanceElement.elementId(), e.getMessage());
@Test
void lockTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV2.lock(compositionElement, instanceElement);
+ acElementListener.lock(compositionElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), null, LockState.LOCKED, StateChangeResult.NO_ERROR, "Locked");
}
@Test
void deleteTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV2.delete(compositionElement, instanceElement);
+ acElementListener.delete(compositionElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.DELETED, null, StateChangeResult.NO_ERROR, "Deleted");
}
@Test
void updateTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV2.update(compositionElement, instanceElement, instanceElement);
+ acElementListener.update(compositionElement, instanceElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.DEPLOYED, null,
StateChangeResult.NO_ERROR, "Update not supported");
@Test
void unlockTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV2.unlock(compositionElement, instanceElement);
+ acElementListener.unlock(compositionElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), null, LockState.UNLOCKED, StateChangeResult.NO_ERROR, "Unlocked");
}
@Test
void primeTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionId = UUID.randomUUID();
var toscaConceptIdentifier = new ToscaConceptIdentifier();
var composition = new CompositionDto(compositionId, Map.of(toscaConceptIdentifier, Map.of()), Map.of());
- acElementListenerV2.prime(composition);
+ acElementListener.prime(composition);
verify(intermediaryApi)
.updateCompositionState(compositionId, AcTypeState.PRIMED, StateChangeResult.NO_ERROR, "Primed");
}
@Test
void deprimeTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionId = UUID.randomUUID();
var toscaConceptIdentifier = new ToscaConceptIdentifier();
var composition = new CompositionDto(compositionId, Map.of(toscaConceptIdentifier, Map.of()), Map.of());
- acElementListenerV2.deprime(composition);
+ acElementListener.deprime(composition);
verify(intermediaryApi)
.updateCompositionState(compositionId, AcTypeState.COMMISSIONED, StateChangeResult.NO_ERROR, "Deprimed");
}
@Test
void migrateTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV2 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV2.migrate(compositionElement, compositionElement, instanceElement, instanceElement, 0);
+ acElementListener.migrate(compositionElement, compositionElement, instanceElement, instanceElement, 0);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.DEPLOYED, null,
StateChangeResult.NO_ERROR, "Migrated");
@Test
void migratePrecheckTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV1 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV1.migratePrecheck(compositionElement, compositionElement, instanceElement, instanceElement);
+ acElementListener.migratePrecheck(compositionElement, compositionElement, instanceElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.DEPLOYED, null,
StateChangeResult.NO_ERROR, "Migration Precheck completed");
@Test
void reviewTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV1 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV1.review(compositionElement, instanceElement);
+ acElementListener.review(compositionElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.DEPLOYED, null,
StateChangeResult.NO_ERROR, "Review completed");
@Test
void prepareTest() throws PfModelException {
var intermediaryApi = mock(ParticipantIntermediaryApi.class);
- var acElementListenerV1 = createAcElementListenerV3(intermediaryApi);
+ var acElementListener = createAcElementListenerV3(intermediaryApi);
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV1.prepare(compositionElement, instanceElement);
+ acElementListener.prepare(compositionElement, instanceElement);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
instanceElement.elementId(), DeployState.UNDEPLOYED, null,
StateChangeResult.NO_ERROR, "Prepare completed");
var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
Map.of(), Map.of());
var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
- acElementListenerV3.rollbackMigration(compositionElement, instanceElement, 1);
+ acElementListenerV3.rollbackMigration(compositionElement, compositionElement, instanceElement,
+ instanceElement, 0);
verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
- instanceElement.elementId(), DeployState.DEPLOYED, null,
- StateChangeResult.NO_ERROR, "Migration rollback done");
+ instanceElement.elementId(), DeployState.DEPLOYED, null,
+ StateChangeResult.NO_ERROR, "Migration rollback done");
}
private AcElementListenerV3 createAcElementListenerV3(ParticipantIntermediaryApi intermediaryApi) {
if (!rollback) {
verify(listener, times(expectedMigrated)).migrate(any(), any(), any(), any(), any(), anyInt());
} else {
- verify(listener, times(expectedMigrated)).rollback(any(), any(), any(), anyInt());
+ verify(listener, times(expectedMigrated)).rollback(any(), any(), any(), any(), any(), anyInt());
}
}
}
instanceElement, instanceElementUpdated, 0);
clearInvocations(listener);
- threadHandler.rollback(messageId, compositionElement, instanceElement, 0);
- verify(listener, timeout(TIMEOUT)).rollbackMigration(compositionElement, instanceElement, 0);
-
+ threadHandler.rollback(messageId, compositionElement, compositionElementTarget,
+ instanceElement, instanceElementUpdated, 0);
+ verify(listener, timeout(TIMEOUT)).rollbackMigration(compositionElement, compositionElementTarget,
+ instanceElement, instanceElementUpdated, 0);
clearInvocations(listener);
threadHandler.undeploy(messageId, compositionElement, instanceElement);
verify(intermediaryApi, timeout(TIMEOUT)).updateAutomationCompositionElementState(instanceId, elementId,
DeployState.DEPLOYED, null, StateChangeResult.FAILED,
"Automation composition element migrate precheck failed");
-
- clearInvocations(listener);
- doThrow(new PfModelException(Status.INTERNAL_SERVER_ERROR, "Error")).when(listener)
- .rollbackMigration(compositionElement, instanceElement, 0);
- threadHandler.rollback(compositionElement.compositionId(), compositionElement, instanceElement, 0);
- verify(intermediaryApi, timeout(TIMEOUT)).updateAutomationCompositionElementState(instanceId, elementId,
- DeployState.DEPLOYED, null, StateChangeResult.FAILED,
- "Automation composition element migrate precheck failed");
}
}
}
import jakarta.ws.rs.core.Response.Status;
import java.util.List;
import java.util.UUID;
-import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
}
var automationCompositionToRollback =
- automationCompositionProvider.getAutomationCompositionRollback(instanceId);
+ automationCompositionProvider.getAutomationCompositionRollback(instanceId);
var acToBeUpdated = new AutomationComposition(automationComposition);
acToBeUpdated.setCompositionTargetId(automationCompositionToRollback.getCompositionId());
acToBeUpdated.setElements(automationCompositionToRollback.getElements().values().stream()
schema:
type: string
format: uuid
- requestBody:
- description: Parameters for the rollback (e.g., target version)
- required: true
- content:
- application/json:
- schema:
- application/yaml:
- schema:
responses:
202:
description: Rollback initiated
import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_VERSIONING;
import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
private int randomServerPort;
@BeforeAll
- public static void setUpBeforeClass() {
+ static void setUpBeforeClass() {
serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
}
@BeforeEach
- public void setUpPort() {
+ void setUpPort() {
super.setHttpPrefix(randomServerPort);
}
var rawresp = invocationBuilder.buildGet().invoke();
assertEquals(Response.Status.OK.getStatusCode(), rawresp.getStatus());
var automationCompositionGet = rawresp.readEntity(AutomationComposition.class);
+ rawresp.close();
assertNotNull(automationCompositionGet);
automationComposition.setLastMsg(automationCompositionGet.getLastMsg());
assertEquals(automationComposition, automationCompositionGet);
.forEach(element -> element.setParticipantId(CommonTestData.getParticipantId()));
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId));
- var resp = invocationBuilder.post(Entity.json(automationComposition));
- assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus());
-
- var instResponse = resp.readEntity(InstantiationResponse.class);
- InstantiationUtils.assertInstantiationResponse(instResponse, automationComposition);
+ try (var resp = invocationBuilder.post(Entity.json(automationComposition))) {
+ assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus());
+ var instResponse = resp.readEntity(InstantiationResponse.class);
+ InstantiationUtils.assertInstantiationResponse(instResponse, automationComposition);
+ }
var automationCompositionsFromDb = instantiationProvider.getAutomationCompositions(
compositionId, automationComposition.getKey().getName(),
automationComposition.getKey().getVersion(), Pageable.unpaged());
instantiationProvider.createAutomationComposition(compositionId, automationCompositionFromRsc);
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId, instResponse.getInstanceId()));
- var resp = invocationBuilder.delete();
- assertEquals(Response.Status.ACCEPTED.getStatusCode(), resp.getStatus());
- instResponse = resp.readEntity(InstantiationResponse.class);
+ try (var resp = invocationBuilder.delete()) {
+ assertEquals(Response.Status.ACCEPTED.getStatusCode(), resp.getStatus());
+ instResponse = resp.readEntity(InstantiationResponse.class);
+ }
InstantiationUtils.assertInstantiationResponse(instResponse, automationCompositionFromRsc);
var automationCompositionsFromDb = instantiationProvider.getAutomationCompositions(compositionId,
instantiationProvider.createAutomationComposition(compositionId, automationCompositionFromRsc);
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId, UUID.randomUUID()));
- var resp = invocationBuilder.delete();
- assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus());
+ try (var resp = invocationBuilder.delete()) {
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus());
+ }
+ }
+
+ @Test
+ void testRollbackNotValid() {
+ var compositionId = createAcDefinitionInDB("RollbackNotFound");
+
+ // instance not found
+ var url = getInstanceEndPoint(compositionId, UUID.randomUUID()) + "/rollback";
+ var invocationBuilder = super.sendRequest(url);
+ try (var resp = invocationBuilder.post(Entity.entity("", MediaType.APPLICATION_JSON))) {
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus());
+ }
+
+ // instance not valid state
+ var automationCompositionFromRsc =
+ InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_CREATE_JSON, "NotValid");
+ automationCompositionFromRsc.setCompositionId(compositionId);
+ var instanceResponce =
+ instantiationProvider.createAutomationComposition(compositionId, automationCompositionFromRsc);
+ url = getInstanceEndPoint(compositionId, instanceResponce.getInstanceId()) + "/rollback";
+ invocationBuilder = super.sendRequest(url);
+ try (var resp = invocationBuilder.post(Entity.entity("", MediaType.APPLICATION_JSON))) {
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus());
+ }
}
@Test
void testDeploy_NotFound() {
var compositionId = createAcDefinitionInDB("Deploy_NotFound");
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId, UUID.randomUUID()));
- var resp = invocationBuilder.put(Entity.json(new AcInstanceStateUpdate()));
- assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus());
+ try (var resp = invocationBuilder.put(Entity.json(new AcInstanceStateUpdate()))) {
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus());
+ }
}
@Test
command.setLockOrder(null);
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId, instResponse.getInstanceId()));
- var resp = invocationBuilder.put(Entity.json(command));
- assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus());
+ try (var resp = invocationBuilder.put(Entity.json(command))) {
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus());
+ }
}
@Test
instantiationUpdate.setLockOrder(null);
var invocationBuilder = super.sendRequest(getInstanceEndPoint(compositionId, instResponse.getInstanceId()));
- var resp = invocationBuilder.put(Entity.json(instantiationUpdate));
- assertEquals(Response.Status.ACCEPTED.getStatusCode(), resp.getStatus());
+ try (var resp = invocationBuilder.put(Entity.json(instantiationUpdate))) {
+ assertEquals(Response.Status.ACCEPTED.getStatusCode(), resp.getStatus());
+ }
}
private UUID createAcDefinitionInDB(String name) {