Skip to content

Commit

Permalink
Merge branch 'main' into konflux/references/main
Browse files Browse the repository at this point in the history
  • Loading branch information
lindseyburnett authored Sep 4, 2024
2 parents 4b2c7aa + c0f3dbb commit 8c0dcd1
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 89 deletions.
1 change: 0 additions & 1 deletion swatch-contracts/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ dependencies {
implementation 'io.quarkus:quarkus-smallrye-reactive-messaging-amqp'
implementation 'io.quarkus:quarkus-smallrye-reactive-messaging-kafka'
implementation 'io.smallrye.reactive:smallrye-reactive-messaging-in-memory'
implementation project(':clients:swatch-internal-subscription-client')
implementation project(':clients:quarkus:subscription-client')
implementation project(':swatch-common-clock')
implementation project(':swatch-common-config-workaround')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,24 @@
*/
package com.redhat.swatch.contract.model;

import com.redhat.swatch.clients.swatch.internal.subscription.api.model.Metric;
import com.redhat.swatch.clients.swatch.internal.subscription.api.resources.ApiException;
import com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi;
import com.redhat.swatch.configuration.registry.Metric;
import com.redhat.swatch.configuration.registry.Variant;
import com.redhat.swatch.contract.exception.ContractsException;
import com.redhat.swatch.contract.exception.ErrorCode;
import com.redhat.swatch.contract.repository.BillingProvider;
import com.redhat.swatch.contract.repository.ContractEntity;
import com.redhat.swatch.contract.repository.SubscriptionEntity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.ProcessingException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;

@Slf4j
@ApplicationScoped
public class MeasurementMetricIdTransformer {
@RestClient @Inject InternalSubscriptionsApi internalSubscriptionsApi;

/**
* Maps all incoming metrics from cloud provider-specific formats/UOMs into the swatch UOM value.
Expand All @@ -58,39 +55,48 @@ public void translateContractMetricIdsToSubscriptionMetricIds(SubscriptionEntity
}

try {
if (subscription.getBillingProvider() == BillingProvider.AWS) {
if (subscription.getBillingProvider() == BillingProvider.AWS
|| subscription.getBillingProvider() == BillingProvider.AZURE) {
for (String tag : subscription.getOffering().getProductTags()) {
var metrics = internalSubscriptionsApi.getMetrics(tag);
// the metricId currently set here is actually the aws Dimension and get translated to the
// metric uom after calculation
checkForUnsupportedMetrics(metrics, subscription);
subscription
.getSubscriptionMeasurements()
.forEach(
measurement ->
metrics.stream()
.filter(
metric ->
Objects.equals(
metric.getAwsDimension(), measurement.getMetricId()))
.findFirst()
.ifPresent(
metric -> {
if (metric.getBillingFactor() != null
&& measurement.getValue() != null) {
measurement.setValue(
measurement.getValue() / metric.getBillingFactor());
}
measurement.setMetricId(metric.getUom());
}));
mapMetricsToSubscription(subscription, tag);
}
}
} catch (ProcessingException | ApiException e) {
} catch (ProcessingException e) {
log.error("Error looking up dimension for metrics", e);
throw new ContractsException(ErrorCode.UNHANDLED_EXCEPTION, e.getMessage());
}
}

private void mapMetricsToSubscription(SubscriptionEntity subscription, String productTag) {
var metrics = Variant.getMetricsForTag(productTag).stream().toList();
// the metricId currently set here is actually the aws/azure Dimension and get translated
// to the
// metric uom after calculation
checkForUnsupportedMetrics(metrics, subscription);
subscription
.getSubscriptionMeasurements()
.forEach(
measurement ->
metrics.stream()
.filter(
metric -> {
String marketplaceMetricId =
subscription.getBillingProvider() == BillingProvider.AWS
? metric.getAwsDimension()
: metric.getAzureDimension();
return Objects.equals(marketplaceMetricId, measurement.getMetricId());
})
.findFirst()
.ifPresent(
metric -> {
if (metric.getBillingFactor() != null && measurement.getValue() != null) {
measurement.setValue(
measurement.getValue() / metric.getBillingFactor());
}
measurement.setMetricId(metric.getId());
}));
}

/**
* Resolves all conflicting metrics from cloud provider-specific formats.
*
Expand All @@ -109,14 +115,20 @@ public void resolveConflictingMetrics(ContractEntity contract) {
// resolve contract measurements with the correct metrics from sync service
// this will keep subscriptions and contract metrics consistent with its dimension to SWATCH UOM
log.debug(
"Resolving conflicting metrics between subscription & contract for {}",
"Resolving conflicting metrics between subscription & contract for {}",
contract.getOrgId());
try {
for (String productTag : contract.getOffering().getProductTags()) {
var metrics =
internalSubscriptionsApi.getMetrics(productTag).stream()
.map(Metric::getAwsDimension)
.collect(Collectors.toSet());
var variantMetrics = Variant.getMetricsForTag(productTag).stream();
Set<String> metrics;
if (BillingProvider.AWS.getValue().equals(contract.getBillingProvider())) {
metrics = variantMetrics.map(Metric::getAwsDimension).collect(Collectors.toSet());
} else if (BillingProvider.AZURE.getValue().equals(contract.getBillingProvider())) {
metrics = variantMetrics.map(Metric::getAzureDimension).collect(Collectors.toSet());
} else {
metrics = Set.of();
}

contract
.getMetrics()
.removeIf(
Expand All @@ -135,7 +147,7 @@ public void resolveConflictingMetrics(ContractEntity contract) {
return false;
});
}
} catch (ProcessingException | ApiException e) {
} catch (ProcessingException e) {
log.error("Error resolving dimensions for contract metrics", e);
throw new ContractsException(ErrorCode.UNHANDLED_EXCEPTION, e.getMessage());
}
Expand All @@ -145,7 +157,14 @@ private void checkForUnsupportedMetrics(List<Metric> metrics, SubscriptionEntity
if (!subscription.getSubscriptionMeasurements().isEmpty()) {
// Will check for correct metrics before translating awsDimension to Metrics UOM
log.debug("Checking for unsupported metricIds");
var supportedSet = metrics.stream().map(Metric::getAwsDimension).collect(Collectors.toSet());
Set<String> supportedSet;
if (subscription.getBillingProvider() == BillingProvider.AWS) {
supportedSet = metrics.stream().map(Metric::getAwsDimension).collect(Collectors.toSet());
} else if (subscription.getBillingProvider() == BillingProvider.AZURE) {
supportedSet = metrics.stream().map(Metric::getAzureDimension).collect(Collectors.toSet());
} else {
supportedSet = Set.of();
}
subscription
.getSubscriptionMeasurements()
.removeIf(m -> !supportedSet.contains(m.getMetricId()));
Expand Down
8 changes: 0 additions & 8 deletions swatch-contracts/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,6 @@ quarkus.rest-client."com.redhat.swatch.clients.subscription.api.resources.Search
# Setting up the timeout to 2 minutes instead of 30 seconds (default)
quarkus.rest-client."com.redhat.swatch.clients.subscription.api.resources.SearchApi".read-timeout=120000

# configuration properties for subscriptions-sync
# Note the keystore and truststore values must be prefixed with either "classpath:" or "file:"
quarkus.rest-client."com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi".url=${SWATCH_INTERNAL_SUBSCRIPTION_ENDPOINT}/api/rhsm-subscriptions
quarkus.rest-client."com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi".trust-store=${clowder.endpoints.swatch-subscription-sync-service.trust-store-path}
quarkus.rest-client."com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi".trust-store-password=${clowder.endpoints.swatch-subscription-sync-service.trust-store-password}
quarkus.rest-client."com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi".trust-store-type=${clowder.endpoints.swatch-subscription-sync-service.trust-store-type}
quarkus.rest-client."com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi".providers=com.redhat.swatch.resteasy.client.SwatchPskHeaderFilter

# configuration properties for product client
rhsm-subscriptions.product.use-stub=${PRODUCT_USE_STUB:false}
# Note the keystore and truststore values must be prefixed with either "classpath:" or "file:"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import com.redhat.swatch.clients.swatch.internal.subscription.api.model.Metric;
import com.redhat.swatch.clients.swatch.internal.subscription.api.resources.ApiException;
import com.redhat.swatch.clients.swatch.internal.subscription.api.resources.InternalSubscriptionsApi;
import com.redhat.swatch.contract.repository.BillingProvider;
import com.redhat.swatch.contract.repository.ContractEntity;
import com.redhat.swatch.contract.repository.ContractMetricEntity;
Expand All @@ -39,44 +34,43 @@
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class MeasurementMetricIdTransformerTest {
@Mock InternalSubscriptionsApi internalSubscriptionsApi;
@InjectMocks MeasurementMetricIdTransformer transformer;

@Test
void testMapsAwsDimensionToMetricId() throws ApiException, RuntimeException {
@ParameterizedTest
@EnumSource(
value = BillingProvider.class,
names = {"AWS", "AZURE"})
void testMapsMarketplaceDimensionToMetricId(BillingProvider billingProvider)
throws RuntimeException {
var subscription = new SubscriptionEntity();
subscription.setBillingProvider(BillingProvider.AWS);
subscription.setBillingProvider(billingProvider);
var measurement1 = new SubscriptionMeasurementEntity();
measurement1.setMetricId("bar");
measurement1.setMetricId("control_plane");
var measurement2 = new SubscriptionMeasurementEntity();
measurement2.setMetricId("foo");
measurement2.setMetricId("four_vcpu_hour");
measurement2.setValue(100.0);
subscription.addSubscriptionMeasurement(measurement1);
subscription.addSubscriptionMeasurement(measurement2);
subscription.setOffering(new OfferingEntity());
subscription.getOffering().setProductTags(Set.of("hello"));
subscription.getOffering().setProductTags(Set.of("rosa"));

when(internalSubscriptionsApi.getMetrics("hello"))
.thenReturn(
List.of(
new Metric().uom("foo1").awsDimension("foo").billingFactor(0.25),
new Metric().uom("bar2").awsDimension("bar").billingFactor(1.0)));
transformer.translateContractMetricIdsToSubscriptionMetricIds(subscription);
assertTrue(
subscription.getSubscriptionMeasurements().stream()
.map(SubscriptionMeasurementEntity::getMetricId)
.collect(Collectors.toList())
.containsAll(Set.of("bar2", "foo1")));
.toList()
.containsAll(Set.of("Instance-hours", "Cores")));
assertEquals(
400.0,
subscription.getSubscriptionMeasurements().stream()
.filter(m -> m.getMetricId().startsWith("foo"))
.filter(m -> m.getMetricId().startsWith("Cores"))
.findFirst()
.orElseThrow()
.getValue());
Expand All @@ -86,28 +80,32 @@ void testMapsAwsDimensionToMetricId() throws ApiException, RuntimeException {
void testNoMappingAttemptedForMissingBillingProvider() throws RuntimeException {
var subscription = new SubscriptionEntity();
var measurement1 = new SubscriptionMeasurementEntity();
measurement1.setMetricId("bar");
measurement1.setMetricId("test1");
var measurement2 = new SubscriptionMeasurementEntity();
measurement2.setMetricId("foo");
measurement2.setMetricId("test2");
subscription.addSubscriptionMeasurement(measurement1);
subscription.addSubscriptionMeasurement(measurement2);
subscription.setOffering(new OfferingEntity());
subscription.getOffering().setProductTags(Set.of("hello"));
subscription.getOffering().setProductTags(Set.of("rosa"));

transformer.translateContractMetricIdsToSubscriptionMetricIds(subscription);
verifyNoInteractions(internalSubscriptionsApi);
assertEquals(
List.of("test1", "test2"),
subscription.getSubscriptionMeasurements().stream()
.map(SubscriptionMeasurementEntity::getMetricId)
.collect(Collectors.toList()));
}

@Test
void testUnsupportedMetricIdAreRemoved() throws ApiException, RuntimeException {
void testUnsupportedMetricIdAreRemoved() throws RuntimeException {
var subscription = new SubscriptionEntity();
subscription.setBillingProvider(BillingProvider.AWS);
var instanceHours = new SubscriptionMeasurementEntity();
instanceHours.setValue(100.0);
instanceHours.setMetricId("control_plane_0");
instanceHours.setMetricId("control_plane");

var cores = new SubscriptionMeasurementEntity();
cores.setMetricId("four_vcpu_0");
cores.setMetricId("four_vcpu_hour");
cores.setValue(100.0);

var unknown = new SubscriptionMeasurementEntity();
Expand All @@ -121,11 +119,6 @@ void testUnsupportedMetricIdAreRemoved() throws ApiException, RuntimeException {
subscription.setOffering(new OfferingEntity());
subscription.getOffering().setProductTags(Set.of("rosa"));

when(internalSubscriptionsApi.getMetrics("rosa"))
.thenReturn(
List.of(
new Metric().uom("Instance-hours").awsDimension("control_plane_0"),
new Metric().uom("Cores").awsDimension("four_vcpu_0").billingFactor(0.25)));
transformer.translateContractMetricIdsToSubscriptionMetricIds(subscription);
assertEquals(
List.of("Instance-hours", "Cores"),
Expand All @@ -135,15 +128,15 @@ void testUnsupportedMetricIdAreRemoved() throws ApiException, RuntimeException {
}

@Test
void testUnsupportedDimensionsAreRemovedFromContracts() throws ApiException, RuntimeException {
void testUnsupportedDimensionsAreRemovedFromContracts() throws RuntimeException {
var contract = new ContractEntity();
contract.setBillingProvider(BillingProvider.AWS.getValue());
var instanceHours = new ContractMetricEntity();
instanceHours.setValue(100.0);
instanceHours.setMetricId("control_plane_0");
instanceHours.setMetricId("control_plane");

var cores = new ContractMetricEntity();
cores.setMetricId("four_vcpu_0");
cores.setMetricId("four_vcpu_hour");
cores.setValue(100.0);

var unknown = new ContractMetricEntity();
Expand All @@ -162,14 +155,9 @@ void testUnsupportedDimensionsAreRemovedFromContracts() throws ApiException, Run
contract.setOffering(new OfferingEntity());
contract.getOffering().setProductTags(Set.of("rosa"));

when(internalSubscriptionsApi.getMetrics("rosa"))
.thenReturn(
List.of(
new Metric().uom("Instance-hours").awsDimension("control_plane_0"),
new Metric().uom("Cores").awsDimension("four_vcpu_0").billingFactor(0.25)));
transformer.resolveConflictingMetrics(contract);
assertEquals(
Set.of("control_plane_0", "four_vcpu_0"),
Set.of("control_plane", "four_vcpu_hour"),
contract.getMetrics().stream()
.map(ContractMetricEntity::getMetricId)
.collect(Collectors.toSet()));
Expand Down

0 comments on commit 8c0dcd1

Please sign in to comment.