Skip to content

Commit

Permalink
Merge branch 'main' into fix-remove-regulation
Browse files Browse the repository at this point in the history
  • Loading branch information
EtienneLt authored Oct 18, 2024
2 parents 51ff55e + 9da0e41 commit 84b38fa
Show file tree
Hide file tree
Showing 11 changed files with 557 additions and 102 deletions.
2 changes: 1 addition & 1 deletion network-store-client-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-network-store-client-parent</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>

<artifactId>powsybl-network-store-client-distribution</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion network-store-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-network-store-client-parent</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>

<artifactId>powsybl-network-store-client</artifactId>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import static org.junit.Assert.*;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.PUT;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
Expand Down Expand Up @@ -960,4 +961,57 @@ private void loadTwoIdentifiablesToCache(String identifiableId1, String identifi
server.verify();
server.reset();
}

@Test
public void testGetExtensionsCacheWithClonedNetwork() throws IOException {
int targetVariantNum = 1;
String targetVariantId = "new_variant";
String identifiableId1 = "GEN";
String identifiableId2 = "GEN1";

// Load the identifiables in the cache
loadTwoIdentifiablesToCache(identifiableId1, identifiableId2);

// Two successive ExtensionAttributes retrieval, only the first should send a REST request, the second uses the cache
ActivePowerControlAttributes apc1 = ActivePowerControlAttributes.builder()
.droop(5.2)
.participate(true)
.participationFactor(0.5)
.build();
GeneratorStartupAttributes gs1 = GeneratorStartupAttributes.builder()
.marginalCost(6.8)
.forcedOutageRate(35)
.plannedOutageRate(30)
.startupCost(28)
.plannedActivePowerSetpoint(5)
.build();
ActivePowerControlAttributes apc2 = ActivePowerControlAttributes.builder()
.droop(5.2)
.participate(true)
.participationFactor(1)
.build();

// Load extensions to cache on initial variant
String multipleExtensionAttributes = objectMapper.writerFor(new TypeReference<Map<String, Map<String, ExtensionAttributes>>>() {
}).writeValueAsString(Map.of(identifiableId1, Map.of(ActivePowerControl.NAME, apc1, GeneratorStartup.NAME, gs1), identifiableId2, Map.of(ActivePowerControl.NAME, apc2)));
server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/identifiables/types/" + ResourceType.GENERATOR + "/extensions"))
.andExpect(method(GET))
.andRespond(withSuccess(multipleExtensionAttributes, MediaType.APPLICATION_JSON));
cachedClient.getAllExtensionsAttributesByResourceType(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.GENERATOR);
server.verify();
server.reset();

// Clone network
server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/to/" + targetVariantNum + "?targetVariantId=" + targetVariantId))
.andExpect(method(PUT))
.andRespond(withSuccess());
cachedClient.cloneNetwork(networkUuid, Resource.INITIAL_VARIANT_NUM, targetVariantNum, targetVariantId);

// Verify that the cache is copied and there is no new fetch
cachedClient.getAllExtensionsAttributesByResourceType(networkUuid, targetVariantNum, ResourceType.GENERATOR);
Map<String, ExtensionAttributes> extensionAttributesByExtensionNameMap = cachedClient.getAllExtensionsAttributesByIdentifiableId(networkUuid, targetVariantNum, ResourceType.GENERATOR, identifiableId1);
assertEquals(2, extensionAttributesByExtensionNameMap.size());
server.verify();
server.reset();
}
}
2 changes: 1 addition & 1 deletion network-store-iidm-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<parent>
<artifactId>powsybl-network-store-client-parent</artifactId>
<groupId>com.powsybl</groupId>
<version>1.19.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>

<artifactId>powsybl-network-store-iidm-impl</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,8 @@ public Optional<Resource<IdentifiableAttributes>> getIdentifiable(UUID networkUu
Optional<Resource<IdentifiableAttributes>> resource = delegate.getIdentifiable(networkUuid, variantNum, id);
resource.ifPresent(r -> {
CollectionCache<IdentifiableAttributes> collection = (CollectionCache<IdentifiableAttributes>) networkContainersCaches.get(r.getType()).getCollection(networkUuid, variantNum);
collection.addResource(r);
// we already checked that the resource is not in the cache so we can directly put it in the cache
collection.addOrReplaceResource(r);
});

identifiableCallCountByNetworkVariant.computeIfAbsent(p, k -> new MutableInt())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
public class CollectionCache<T extends IdentifiableAttributes> {

/**
* Resources indexed by id.
* Resources indexed by id. <br/>
* We enforce a single resource per variant because they are referenced both in these maps
* and directly in any identifiable object created via the IIDM API. <br/>
* Overwriting a resource creates a new reference, which breaks synchronization with
* the IIDM object managed in the NetworkObjectIndex.
*/
private final Map<String, Resource<T>> resources = new HashMap<>();

Expand All @@ -34,7 +38,11 @@ public class CollectionCache<T extends IdentifiableAttributes> {
private boolean fullyLoaded = false;

/**
* Resources indexed by container id. A container is either a substation or a voltage level.
* Resources indexed by container id. A container is either a substation or a voltage level. <br/>
* We enforce a single resource per variant because they are referenced both in these maps
* and directly in any identifiable object created via the IIDM API. <br/>
* Overwriting a resource creates a new reference, which breaks synchronization with
* the IIDM object managed in the NetworkObjectIndex.
*/
private final Map<String, Map<String, Resource<T>>> resourcesByContainerId = new HashMap<>();

Expand Down Expand Up @@ -147,7 +155,8 @@ public Optional<Resource<T>> getResource(UUID networkUuid, int variantNum, Strin
resource = oneLoaderFunction.apply(networkUuid, variantNum, id).orElse(null);
// if resource has been found on server side we add it to the cache
if (resource != null) {
addResource(resource);
// we already checked that the resource is not in the cache so we can directly put it in the cache
addOrReplaceResource(resource);
}
}
}
Expand All @@ -161,8 +170,10 @@ private void loadAll(UUID networkUuid, int variantNum) {
List<Resource<T>> resourcesToAdd = allLoaderFunction.apply(networkUuid, variantNum);

// we update the full cache and set it as fully loaded
// notice: it might overwrite already loaded resource (single or container)
resourcesToAdd.forEach(resource -> resources.put(resource.getId(), resource));
// notice: even if it adds some checks and reduces performance by a tiny bit, we avoid to overwrite already
// loaded resource (single or container) because they are referenced in the resources or resourcesByContainerId map,
// but also directly in any identifiable with the iidm api.
resourcesToAdd.forEach(resource -> resources.putIfAbsent(resource.getId(), resource));
fullyLoaded = true;

// we update by container cache
Expand All @@ -172,7 +183,10 @@ private void loadAll(UUID networkUuid, int variantNum) {
Set<String> containerIds = ((Contained) attributes).getContainerIds();
containerIds.forEach(containerId -> {
// we add container resources and update container fully loaded status
getResourcesByContainerId(containerId).put(resource.getId(), resource);
// notice: even if it adds some checks and reduces performance by a tiny bit, we avoid to overwrite already
// loaded resource (single or container) because they are referenced in the resources or resourcesByContainerId map,
// but also directly in any identifiable with the iidm api.
getResourcesByContainerId(containerId).putIfAbsent(resource.getId(), resource);
containerFullyLoaded.add(containerId);
});
}
Expand Down Expand Up @@ -213,20 +227,27 @@ public List<Resource<T>> getContainerResources(UUID networkUuid, int variantNum,
List<Resource<T>> resourcesToAdd = containerLoaderFunction.apply(networkUuid, variantNum, containerId)
.stream().filter(resource -> !removedResources.contains(resource.getId())).collect(Collectors.toList());

// by container cache update
getResourcesByContainerId(containerId).putAll(resourcesToAdd.stream().collect(Collectors.toMap(Resource::getId, resource -> resource)));
containerFullyLoaded.add(containerId);

// full cache update
resourcesToAdd.forEach(resource -> {
resources.put(resource.getId(), resource);
removedResources.remove(resource.getId());
String resourceId = resource.getId();
// notice: even if it adds some checks and reduces performance by a tiny bit, we avoid to overwrite already
// loaded resource (single or container) because they are referenced in the resources or resourcesByContainerId map,
// but also directly in any identifiable with the iidm api.
getResourcesByContainerId(containerId).putIfAbsent(resourceId, resource);
resources.putIfAbsent(resourceId, resource);
removedResources.remove(resourceId);
});
containerFullyLoaded.add(containerId);
}
return new ArrayList<>(getResourcesByContainerId(containerId).values());
}

public void addResource(Resource<T> resource) {
/**
* Adds or replaces the given resource in the cache. <br/>
* If the resource already exists in the cache, it will be overridden.
*
* @param resource the resource to add or replace in the cache
*/
public void addOrReplaceResource(Resource<T> resource) {
Objects.requireNonNull(resource);

// full cache update
Expand All @@ -247,16 +268,21 @@ public void addResource(Resource<T> resource) {
* @param resource the newly created resources
*/
public void createResource(Resource<T> resource) {
addResource(resource);
String resourceId = resource.getId();
if (resources.containsKey(resourceId)) {
throw new PowsyblException("The collection cache already contains a " + resource.getType() + " with the id '" + resourceId + "'");
}
// we already checked that the resource is not in the cache so we can directly put it in the cache
addOrReplaceResource(resource);
}

/**
* Update (replace) a resource of the collection.
* Replace resource of the collection with {@code resource}.
*
* @param resource the resources to update
* @param resource the resource to update
*/
public void updateResource(Resource<T> resource) {
addResource(resource);
addOrReplaceResource(resource);
}

/**
Expand Down Expand Up @@ -379,12 +405,15 @@ private boolean isExtensionAttributesCached(String id, String extensionName) {
}

/**
* Add extension attributes in the cache for single extension attributes loading
* Add extension attributes in the cache for single extension attributes loading.<br/>
* This method is only used to get extension attributes from the server so even if it adds some checks and reduces performance by a tiny bit,
* we avoid to overwrite already loaded extension attributes because they are referenced in the extensionAttributes field of the resources or resourcesByContainerId map,
* but also directly in any identifiable with the iidm api.
*/
private void addExtensionAttributesToCache(String identifiableId, String extensionName, ExtensionAttributes extensionAttributes) {
Objects.requireNonNull(extensionAttributes);

getCachedExtensionAttributes(identifiableId).put(extensionName, extensionAttributes);
getCachedExtensionAttributes(identifiableId).putIfAbsent(extensionName, extensionAttributes);
Set<String> extensions = removedExtensionAttributes.get(identifiableId);
if (extensions != null) {
extensions.remove(extensionName);
Expand All @@ -406,10 +435,13 @@ public Map<String, ExtensionAttributes> getAllExtensionsAttributesByResourceType
extensionAttributesMap.forEach((identifiableId, extensionAttributes) -> addExtensionAttributesToCache(identifiableId, extensionName, extensionAttributes));
fullyLoadedExtensionsByExtensionName.add(extensionName);
}
return resources.entrySet()
.stream()
.filter(resourceEntry -> resourceEntry.getValue().getAttributes().getExtensionAttributes().containsKey(extensionName))
.collect(Collectors.toMap(Map.Entry::getKey, resourceEntry -> resourceEntry.getValue().getAttributes().getExtensionAttributes().get(extensionName)));
//TODO This method is only used to load extension attributes in the collection cache when using preloading collection.
// The return is never used by the client as the call to getAllExtensionsAttributesByResourceTypeAndExtensionName() is always followed
// by a call to getExtensionAttributes(). The latter returns something meaningful for the client
// and it's used in the identifiable.getExtension() method. The map extensionAttributesMap can't be stored in the cache to be returned
// as we can't ensure synchronization with the resources map (if extensions or identifiables are updated/removed).
// We should refactor this method to return void.
return null;
}

/**
Expand All @@ -425,7 +457,7 @@ public Map<String, ExtensionAttributes> getAllExtensionsAttributesByIdentifiable
Map<String, ExtensionAttributes> extensionAttributes = delegate.getAllExtensionsAttributesByIdentifiableId(networkUuid, variantNum, type, identifiableId);
if (extensionAttributes != null) {
addAllExtensionAttributesToCache(identifiableId, extensionAttributes);
return extensionAttributes;
return getCachedExtensionAttributes(identifiableId);
}
}
return Map.of();
Expand All @@ -440,12 +472,15 @@ private boolean isExtensionAttributesCached(String identifiableId) {
}

/**
* Add extension attributes to the cache when loading all the extension attributes of an identifiable
* Add extension attributes to the cache when loading all the extension attributes of an identifiable.<br/>
* This method is only used to get extension attributes from the server so even if it adds some checks and reduces performance by a tiny bit,
* we avoid to overwrite already loaded extension attributes because they are referenced in the extensionAttributes field of the resources or resourcesByContainerId map,
* but also directly in any identifiable with the iidm api.
*/
private void addAllExtensionAttributesToCache(String id, Map<String, ExtensionAttributes> extensionAttributes) {
Objects.requireNonNull(extensionAttributes);

getCachedExtensionAttributes(id).putAll(extensionAttributes);
extensionAttributes.forEach(getCachedExtensionAttributes(id)::putIfAbsent);
fullyLoadedExtensionsByIdentifiableIds.add(id);
removedExtensionAttributes.remove(id);
}
Expand All @@ -462,10 +497,13 @@ public Map<String, Map<String, ExtensionAttributes>> getAllExtensionsAttributesB
extensionAttributesMap.forEach(this::addAllExtensionAttributesToCache);
fullyLoadedExtensions = true;
}
return resources.entrySet()
.stream()
.filter(resourceEntry -> !resourceEntry.getValue().getAttributes().getExtensionAttributes().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, resourceEntry -> resourceEntry.getValue().getAttributes().getExtensionAttributes()));
//TODO This method is only used to load extension attributes in the collection cache when using preloading collection.
// The return is never used by the client as the call to getAllExtensionsAttributesByResourceType() is always followed
// by a call to getAllExtensionsAttributesByIdentifiableId(). The latter returns something meaningful for the client
// and it's used in the identifiable.getExtensions() method. The map extensionAttributesMap can't be stored in the cache to be returned
// as we can't ensure synchronization with the resources map (if extensions or identifiables are updated/removed).
// We should refactor this method to return void.
return null;
}

public void removeExtensionAttributesByExtensionName(String identifiableId, String extensionName) {
Expand Down
Loading

0 comments on commit 84b38fa

Please sign in to comment.