diff --git a/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-docs.adoc b/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-docs.adoc index cb16c0e..890c83a 100644 --- a/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-docs.adoc +++ b/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-docs.adoc @@ -23,6 +23,10 @@ quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_y quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.base-package=gen.org.tkit.onecx.product.store.product.v1 quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.return-response=true quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.enable-security-generation=false +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.config-key=product_store_client +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.base-package=gen.org.tkit.onecx.product.store.slot.v1 +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.enable-security-generation=false ---- ==== @@ -63,5 +67,20 @@ app: kcConfig: defaultClientScopes: [ ocx-ps-product:write ] +---- + + + fieldRef: + fieldPath: metadata.namespace + serviceAccount: + enabled: true + operator: + keycloak: + client: + enabled: true + spec: + kcConfig: + defaultClientScopes: [ ocx-ps-product:write ] + ---- diff --git a/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-extensions.adoc b/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-extensions.adoc index f57a7e6..6d94344 100644 --- a/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-extensions.adoc +++ b/docs/modules/onecx-product-store-operator/pages/onecx-product-store-operator-extensions.adoc @@ -72,7 +72,7 @@ h| Version | https://onecx.github.io/docs/onecx-quarkus/current/onecx-quarkus/onecx-core.html[Link] | -| 0.32.0 +| 0.34.0 | quarkus-smallrye-health @@ -91,13 +91,13 @@ h| Version | | -| 6.7.3 +| 6.8.3 | quarkus-operator-sdk | | -| 6.7.3 +| 6.8.3 | quarkus-oidc-client diff --git a/pom.xml b/pom.xml index 9fb969d..a7c91eb 100644 --- a/pom.xml +++ b/pom.xml @@ -114,18 +114,32 @@ download-maven-plugin + product-operator-api generate-resources wget + + https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-operator-product-v1.yaml + target/tmp/openapi + onecx-product-store-operator-product-v1.yaml + true + + + + slot-operator-api + generate-resources + + wget + + + https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-operator-slot-v1.yaml + target/tmp/openapi + onecx-product-store-operator-slot-v1.yaml + true + - - https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-operator-product-v1.yaml - target/tmp/openapi - onecx-product-store-operator-product-v1.yaml - true - diff --git a/src/main/helm/templates/role.yaml b/src/main/helm/templates/role.yaml index 2145ec9..40dee34 100644 --- a/src/main/helm/templates/role.yaml +++ b/src/main/helm/templates/role.yaml @@ -9,6 +9,9 @@ rules: - products - products/status - products/finalizers + - slots + - slots/status + - slots/finalizers verbs: - get - list diff --git a/src/main/java/org/tkit/onecx/product/store/operator/LeaderConfiguration.java b/src/main/java/org/tkit/onecx/product/store/operator/LeaderConfiguration.java index 1299fa5..ae2a8ea 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/LeaderConfiguration.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/LeaderConfiguration.java @@ -10,4 +10,4 @@ public class LeaderConfiguration extends LeaderElectionConfiguration { public LeaderConfiguration(OperatorConfig config) { super(config.leaderElectionConfig().leaseName()); } -} +} \ No newline at end of file diff --git a/src/main/java/org/tkit/onecx/product/store/operator/OperatorConfig.java b/src/main/java/org/tkit/onecx/product/store/operator/OperatorConfig.java index f49c663..47df7ef 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/OperatorConfig.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/OperatorConfig.java @@ -32,4 +32,4 @@ interface LeaderElectionConfig { @WithDefault("onecx-product-store-operator-lease") String leaseName(); } -} +} \ No newline at end of file diff --git a/src/main/java/org/tkit/onecx/product/store/operator/client/ProductStoreService.java b/src/main/java/org/tkit/onecx/product/store/operator/client/ProductStoreService.java index 16fcdcb..5f40b6d 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/client/ProductStoreService.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/client/ProductStoreService.java @@ -6,12 +6,16 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.tkit.onecx.product.store.operator.Product; -import org.tkit.onecx.product.store.operator.ProductSpec; import org.tkit.onecx.product.store.operator.client.mappers.ProductStoreMapper; +import org.tkit.onecx.product.store.operator.product.Product; +import org.tkit.onecx.product.store.operator.product.ProductSpec; +import org.tkit.onecx.product.store.operator.slot.Slot; +import org.tkit.onecx.product.store.operator.slot.SlotSpec; import gen.org.tkit.onecx.product.store.product.v1.api.OperatorProductApi; import gen.org.tkit.onecx.product.store.product.v1.model.UpdateProductRequest; +import gen.org.tkit.onecx.product.store.slot.v1.api.OperatorSlotApi; +import gen.org.tkit.onecx.product.store.slot.v1.model.UpdateSlotRequest; @ApplicationScoped public class ProductStoreService { @@ -25,6 +29,10 @@ public class ProductStoreService { @RestClient OperatorProductApi client; + @Inject + @RestClient + OperatorSlotApi slotClient; + public int updateProduct(Product product) { ProductSpec spec = product.getSpec(); UpdateProductRequest dto = mapper.map(spec); @@ -34,4 +42,13 @@ public int updateProduct(Product product) { } } + public int updateSlot(Slot slot) { + SlotSpec spec = slot.getSpec(); + UpdateSlotRequest dto = mapper.map(spec); + try (var response = slotClient.createOrUpdateSlot(spec.getProductName(), spec.getAppId(), dto)) { + log.info("Update micro-fronted response {}", response.getStatus()); + return response.getStatus(); + } + } + } diff --git a/src/main/java/org/tkit/onecx/product/store/operator/client/mappers/ProductStoreMapper.java b/src/main/java/org/tkit/onecx/product/store/operator/client/mappers/ProductStoreMapper.java index 5a42206..02724b9 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/client/mappers/ProductStoreMapper.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/client/mappers/ProductStoreMapper.java @@ -2,13 +2,18 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.tkit.onecx.product.store.operator.ProductSpec; +import org.tkit.onecx.product.store.operator.product.ProductSpec; +import org.tkit.onecx.product.store.operator.slot.SlotSpec; import gen.org.tkit.onecx.product.store.product.v1.model.UpdateProductRequest; +import gen.org.tkit.onecx.product.store.slot.v1.model.UpdateSlotRequest; @Mapper public interface ProductStoreMapper { @Mapping(target = "undeployed", constant = "false") UpdateProductRequest map(ProductSpec spec); + + @Mapping(target = "undeployed", constant = "false") + UpdateSlotRequest map(SlotSpec spec); } diff --git a/src/main/java/org/tkit/onecx/product/store/operator/Product.java b/src/main/java/org/tkit/onecx/product/store/operator/product/Product.java similarity index 86% rename from src/main/java/org/tkit/onecx/product/store/operator/Product.java rename to src/main/java/org/tkit/onecx/product/store/operator/product/Product.java index 0f5ec88..1669850 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/Product.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/product/Product.java @@ -1,4 +1,4 @@ -package org.tkit.onecx.product.store.operator; +package org.tkit.onecx.product.store.operator.product; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; diff --git a/src/main/java/org/tkit/onecx/product/store/operator/ProductController.java b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductController.java similarity index 98% rename from src/main/java/org/tkit/onecx/product/store/operator/ProductController.java rename to src/main/java/org/tkit/onecx/product/store/operator/product/ProductController.java index 8ca9c5c..2014037 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/ProductController.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductController.java @@ -1,4 +1,4 @@ -package org.tkit.onecx.product.store.operator; +package org.tkit.onecx.product.store.operator.product; import jakarta.inject.Inject; import jakarta.ws.rs.WebApplicationException; diff --git a/src/main/java/org/tkit/onecx/product/store/operator/ProductSpec.java b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductSpec.java similarity index 97% rename from src/main/java/org/tkit/onecx/product/store/operator/ProductSpec.java rename to src/main/java/org/tkit/onecx/product/store/operator/product/ProductSpec.java index e411328..4a3013e 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/ProductSpec.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductSpec.java @@ -1,4 +1,4 @@ -package org.tkit.onecx.product.store.operator; +package org.tkit.onecx.product.store.operator.product; import java.util.Set; diff --git a/src/main/java/org/tkit/onecx/product/store/operator/ProductStatus.java b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductStatus.java similarity index 95% rename from src/main/java/org/tkit/onecx/product/store/operator/ProductStatus.java rename to src/main/java/org/tkit/onecx/product/store/operator/product/ProductStatus.java index 5f1d2e7..a02bb7d 100644 --- a/src/main/java/org/tkit/onecx/product/store/operator/ProductStatus.java +++ b/src/main/java/org/tkit/onecx/product/store/operator/product/ProductStatus.java @@ -1,4 +1,4 @@ -package org.tkit.onecx.product.store.operator; +package org.tkit.onecx.product.store.operator.product; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/org/tkit/onecx/product/store/operator/slot/Slot.java b/src/main/java/org/tkit/onecx/product/store/operator/slot/Slot.java new file mode 100644 index 0000000..51c0b0d --- /dev/null +++ b/src/main/java/org/tkit/onecx/product/store/operator/slot/Slot.java @@ -0,0 +1,11 @@ +package org.tkit.onecx.product.store.operator.slot; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Version("v1") +@Group("onecx.tkit.org") +public class Slot extends CustomResource implements Namespaced { +} diff --git a/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotController.java b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotController.java new file mode 100644 index 0000000..dc91726 --- /dev/null +++ b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotController.java @@ -0,0 +1,96 @@ +package org.tkit.onecx.product.store.operator.slot; + +import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE; + +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tkit.onecx.product.store.operator.client.ProductStoreService; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; + +@ControllerConfiguration(name = "slot", namespaces = WATCH_CURRENT_NAMESPACE, onAddFilter = SlotController.SlotAddFilter.class, onUpdateFilter = SlotController.SlotUpdateFilter.class) +public class SlotController implements Reconciler, ErrorStatusHandler { + + private static final Logger log = LoggerFactory.getLogger(SlotController.class); + + @Inject + ProductStoreService service; + + @Override + public UpdateControl reconcile(Slot slot, Context context) + throws Exception { + + String appId = slot.getSpec().getAppId(); + String productName = slot.getSpec().getProductName(); + String name = slot.getSpec().getName(); + + log.info("Reconcile microservice: {} for product: {}, name: {}", appId, productName, name); + int responseCode = service.updateSlot(slot); + + updateStatusPojo(slot, responseCode); + log.info("Microservice '{}' reconciled - updating status", slot.getMetadata().getName()); + return UpdateControl.updateStatus(slot); + + } + + @Override + public ErrorStatusUpdateControl updateErrorStatus(Slot slot, + Context context, Exception e) { + + int responseCode = -1; + if (e.getCause() instanceof WebApplicationException re) { + responseCode = re.getResponse().getStatus(); + } + + log.error("Error reconcile resource", e); + var status = new SlotStatus(); + status.setRequestProductName(null); + status.setRequestAppId(null); + status.setRequestName(null); + status.setResponseCode(responseCode); + status.setStatus(SlotStatus.Status.ERROR); + status.setMessage(e.getMessage()); + slot.setStatus(status); + return ErrorStatusUpdateControl.updateStatus(slot); + } + + private void updateStatusPojo(Slot slot, int responseCode) { + var result = new SlotStatus(); + var spec = slot.getSpec(); + result.setRequestProductName(spec.getProductName()); + result.setRequestAppId(spec.getAppId()); + result.setRequestName(spec.getName()); + result.setResponseCode(responseCode); + var status = switch (responseCode) { + case 201: + yield SlotStatus.Status.CREATED; + case 200: + yield SlotStatus.Status.UPDATED; + default: + yield SlotStatus.Status.UNDEFINED; + }; + result.setStatus(status); + slot.setStatus(result); + } + + public static class SlotAddFilter implements OnAddFilter { + + @Override + public boolean accept(Slot resource) { + return resource.getSpec() != null; + } + } + + public static class SlotUpdateFilter implements OnUpdateFilter { + + @Override + public boolean accept(Slot newResource, Slot oldResource) { + return newResource.getSpec() != null; + } + } +} diff --git a/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotSpec.java b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotSpec.java new file mode 100644 index 0000000..c4c96a8 --- /dev/null +++ b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotSpec.java @@ -0,0 +1,68 @@ +package org.tkit.onecx.product.store.operator.slot; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SlotSpec { + + @JsonProperty("appId") + private String appId; + + @JsonProperty("productName") + private String productName; + + @JsonProperty("description") + private String description; + + @JsonProperty("name") + private String name; + + @JsonProperty(value = "deprecated") + private boolean deprecated = false; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + + public boolean isDeprecated() { + return deprecated; + } + + @Override + public String toString() { + return "SlotSpec{" + "appId='" + appId + "', productName='" + productName + "', name='" + name + "'}"; + } +} diff --git a/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotStatus.java b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotStatus.java new file mode 100644 index 0000000..d4e2198 --- /dev/null +++ b/src/main/java/org/tkit/onecx/product/store/operator/slot/SlotStatus.java @@ -0,0 +1,86 @@ +package org.tkit.onecx.product.store.operator.slot; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; + +public class SlotStatus extends ObservedGenerationAwareStatus { + + @JsonProperty("productName") + private String requestProductName; + + @JsonProperty("appId") + private String requestAppId; + + @JsonProperty("name") + private String requestName; + + @JsonProperty("responseCode") + private int responseCode; + + @JsonProperty("status") + private Status status; + + @JsonProperty("message") + private String message; + + public enum Status { + + ERROR, + + CREATED, + + UPDATED, + + UNDEFINED; + } + + public String getRequestProductName() { + return requestProductName; + } + + public void setRequestProductName(String requestProductName) { + this.requestProductName = requestProductName; + } + + public String getRequestAppId() { + return requestAppId; + } + + public void setRequestAppId(String requestAppId) { + this.requestAppId = requestAppId; + } + + public String getRequestName() { + return requestName; + } + + public void setRequestName(String requestName) { + this.requestName = requestName; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9f31ef3..983d0dd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,11 +17,18 @@ quarkus.kubernetes-client.devservices.override-kubeconfig=true quarkus.operator-sdk.crd.validate=false quarkus.operator-sdk.helm.enabled=true quarkus.openapi-generator.codegen.input-base-dir=target/tmp/openapi + +# PRODUCT quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.config-key=product_store_client quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.base-package=gen.org.tkit.onecx.product.store.product.v1 quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.return-response=true quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_product_v1_yaml.enable-security-generation=false +# SLOT +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.config-key=product_store_client +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.base-package=gen.org.tkit.onecx.product.store.slot.v1 +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_slot_v1_yaml.enable-security-generation=false # TEST %test.quarkus.mockserver.devservices.config-class-path=true diff --git a/src/test/java/org/tkit/onecx/product/store/operator/LeaderConfigurationTest.java b/src/test/java/org/tkit/onecx/product/store/operator/LeaderConfigurationTest.java index d5c261d..0ce8e9a 100644 --- a/src/test/java/org/tkit/onecx/product/store/operator/LeaderConfigurationTest.java +++ b/src/test/java/org/tkit/onecx/product/store/operator/LeaderConfigurationTest.java @@ -23,6 +23,7 @@ void testLeaderConfiguration() { assertThat(dataConfig).isNotNull(); assertThat(dataConfig.leaderElectionConfig()).isNotNull(); assertThat(leaderConfiguration).isNotNull(); - assertThat(leaderConfiguration.getLeaseName()).isNotNull().isEqualTo(dataConfig.leaderElectionConfig().leaseName()); + assertThat(leaderConfiguration.getLeaseName()).isNotNull() + .isEqualTo(dataConfig.leaderElectionConfig().leaseName()); } } diff --git a/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerResponseTest.java b/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerResponseTest.java index cf979c0..e5b10e7 100644 --- a/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerResponseTest.java +++ b/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerResponseTest.java @@ -9,6 +9,10 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.tkit.onecx.product.store.operator.client.ProductStoreService; +import org.tkit.onecx.product.store.operator.product.Product; +import org.tkit.onecx.product.store.operator.product.ProductController; +import org.tkit.onecx.product.store.operator.product.ProductSpec; +import org.tkit.onecx.product.store.operator.product.ProductStatus; import org.tkit.onecx.product.store.test.AbstractTest; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; diff --git a/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerTest.java b/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerTest.java index 53b6db4..58a204a 100644 --- a/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerTest.java +++ b/src/test/java/org/tkit/onecx/product/store/operator/ProductControllerTest.java @@ -17,6 +17,9 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tkit.onecx.product.store.operator.product.Product; +import org.tkit.onecx.product.store.operator.product.ProductSpec; +import org.tkit.onecx.product.store.operator.product.ProductStatus; import org.tkit.onecx.product.store.test.AbstractTest; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; diff --git a/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerResponseTest.java b/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerResponseTest.java new file mode 100644 index 0000000..6820820 --- /dev/null +++ b/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerResponseTest.java @@ -0,0 +1,54 @@ +package org.tkit.onecx.product.store.operator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.onecx.product.store.operator.client.ProductStoreService; +import org.tkit.onecx.product.store.operator.slot.Slot; +import org.tkit.onecx.product.store.operator.slot.SlotController; +import org.tkit.onecx.product.store.operator.slot.SlotSpec; +import org.tkit.onecx.product.store.operator.slot.SlotStatus; +import org.tkit.onecx.product.store.test.AbstractTest; + +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class SlotControllerResponseTest extends AbstractTest { + + @InjectMock + ProductStoreService productStoreService; + + @Inject + SlotController controller; + + @BeforeEach + void beforeAll() { + Mockito.when(productStoreService.updateSlot(any())).thenReturn(404); + } + + @Test + void testWrongResponse() throws Exception { + + var s = new SlotSpec(); + s.setProductName("product"); + s.setAppId("m1"); + s.setName("m1"); + + var m = new Slot(); + m.setSpec(s); + + UpdateControl result = controller.reconcile(m, null); + assertThat(result).isNotNull(); + assertThat(result.getResource()).isNotNull(); + assertThat(result.getResource().getStatus()).isNotNull(); + assertThat(result.getResource().getStatus().getStatus()).isNotNull().isEqualTo(SlotStatus.Status.UNDEFINED); + + } +} diff --git a/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerTest.java b/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerTest.java new file mode 100644 index 0000000..aee60e1 --- /dev/null +++ b/src/test/java/org/tkit/onecx/product/store/operator/SlotControllerTest.java @@ -0,0 +1,157 @@ +package org.tkit.onecx.product.store.operator; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.stream.Stream; + +import jakarta.inject.Inject; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tkit.onecx.product.store.operator.slot.Slot; +import org.tkit.onecx.product.store.operator.slot.SlotSpec; +import org.tkit.onecx.product.store.operator.slot.SlotStatus; +import org.tkit.onecx.product.store.test.AbstractTest; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.Operator; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class SlotControllerTest extends AbstractTest { + + static final Logger log = LoggerFactory.getLogger(SlotControllerTest.class); + + @Inject + Operator operator; + + @Inject + KubernetesClient client; + + @BeforeAll + public static void init() { + Awaitility.setDefaultPollDelay(2, SECONDS); + Awaitility.setDefaultPollInterval(2, SECONDS); + Awaitility.setDefaultTimeout(10, SECONDS); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void SlotTest(String name, SlotSpec spec, SlotStatus.Status status) { + + operator.start(); + + Slot slot = new Slot(); + slot.setMetadata(new ObjectMetaBuilder().withName(name).withNamespace(client.getNamespace()).build()); + slot.setSpec(spec); + + client.resource(slot).serverSideApply(); + + await().pollDelay(2, SECONDS).untilAsserted(() -> { + SlotStatus mfeStatus = client.resource(slot).get().getStatus(); + assertThat(mfeStatus).isNotNull(); + assertThat(mfeStatus.getStatus()).isNotNull().isEqualTo(status); + }); + } + + private static Stream provideTestData() { + return Stream.of( + Arguments.of("test-1", createSpec("test-1", "product-test", "test1"), SlotStatus.Status.CREATED), + Arguments.of("test-2", createSpec("test-2", "product-test-2", "test2"), SlotStatus.Status.CREATED), + Arguments.of("test-3", createSpec("test-3", "product-test-2", "test3"), SlotStatus.Status.UPDATED), + Arguments.of("test-error-1", createSpec("test-error-1", "product-test-2", "test2"), + SlotStatus.Status.ERROR), + Arguments.of("test-error-2", createSpec("test-error-2", "product-test-2", "test2"), + SlotStatus.Status.ERROR)); + } + + private static SlotSpec createSpec(String appId, String productName, String name) { + SlotSpec spec = new SlotSpec(); + spec.setAppId(appId); + spec.setProductName(productName); + spec.setName(name); + spec.setDescription("description"); + return spec; + } + + @Test + void SlotEmptySpecTest() { + + operator.start(); + + Slot slot = new Slot(); + slot.setMetadata(new ObjectMetaBuilder().withName("empty-spec").withNamespace(client.getNamespace()).build()); + slot.setSpec(new SlotSpec()); + + client.resource(slot).serverSideApply(); + + await().pollDelay(2, SECONDS).untilAsserted(() -> { + SlotStatus mfeStatus = client.resource(slot).get().getStatus(); + assertThat(mfeStatus).isNotNull(); + assertThat(mfeStatus.getStatus()).isNotNull().isEqualTo(SlotStatus.Status.ERROR); + }); + } + + @Test + void SlotNullSpecTest() { + + operator.start(); + + Slot slot = new Slot(); + slot.setMetadata(new ObjectMetaBuilder().withName("null-spec").withNamespace(client.getNamespace()).build()); + slot.setSpec(null); + + client.resource(slot).serverSideApply(); + + await().pollDelay(4, SECONDS).untilAsserted(() -> { + SlotStatus mfeStatus = client.resource(slot).get().getStatus(); + assertThat(mfeStatus).isNull(); + }); + + } + + @Test + void SlotUpdateEmptySpecTest() { + + operator.start(); + + var m = new SlotSpec(); + m.setAppId("test-1"); + m.setProductName("product-test"); + + Slot slot = new Slot(); + slot + .setMetadata(new ObjectMetaBuilder().withName("to-update-spec").withNamespace(client.getNamespace()).build()); + slot.setSpec(m); + + log.info("Creating test slot object: {}", slot); + client.resource(slot).serverSideApply(); + + await().pollDelay(2, SECONDS).untilAsserted(() -> { + SlotStatus mfeStatus = client.resource(slot).get().getStatus(); + assertThat(mfeStatus).isNotNull(); + assertThat(mfeStatus.getStatus()).isNotNull().isEqualTo(SlotStatus.Status.CREATED); + }); + + client.resource(slot).inNamespace(client.getNamespace()) + .edit(s -> { + s.setSpec(null); + return s; + }); + + await().pollDelay(4, SECONDS).untilAsserted(() -> { + SlotStatus mfeStatus = client.resource(slot).get().getStatus(); + assertThat(mfeStatus).isNotNull(); + assertThat(mfeStatus.getStatus()).isNotNull().isEqualTo(SlotStatus.Status.CREATED); + }); + } +} diff --git a/src/test/resources/mockserver/onecx-product-store-slot-mock.json b/src/test/resources/mockserver/onecx-product-store-slot-mock.json new file mode 100644 index 0000000..424d021 --- /dev/null +++ b/src/test/resources/mockserver/onecx-product-store-slot-mock.json @@ -0,0 +1,52 @@ +[ + { + "id": "6", + "httpRequest": { + "method" : "PUT", + "path": "/operator/slot/v1/product-test/test-1" + }, + "httpResponse": { + "statusCode": 201 + } + }, + { + "id": "7", + "httpRequest": { + "method" : "PUT", + "path": "/operator/slot/v1/product-test-2/test-2" + }, + "httpResponse": { + "statusCode": 201 + } + }, + { + "id": "8", + "httpRequest": { + "method" : "PUT", + "path": "/operator/slot/v1/product-test-2/test-3" + }, + "httpResponse": { + "statusCode": 200 + } + }, + { + "id": "9", + "httpRequest": { + "method" : "PUT", + "path": "/operator/slot/v1/product-test-2/test-error-1" + }, + "httpResponse": { + "statusCode": 500 + } + }, + { + "id": "10", + "httpRequest": { + "method" : "PUT", + "path": "/operator/slot/v1/product-test-2/test-error-2" + }, + "httpResponse": { + "statusCode": 400 + } + } +] \ No newline at end of file