diff --git a/src/main/java/org/etsi/osl/cridge/KubernetesClientResource.java b/src/main/java/org/etsi/osl/cridge/KubernetesClientResource.java index 5523cab5e598157c02bae1e8939569646823b9c6..5380cc6b92169569ffb5a32b8eb3c03e2a45dd87 100644 --- a/src/main/java/org/etsi/osl/cridge/KubernetesClientResource.java +++ b/src/main/java/org/etsi/osl/cridge/KubernetesClientResource.java @@ -429,7 +429,7 @@ public class KubernetesClientResource { // logger.debug("Deploy the following CR NORMALIZED:" ); // logger.debug("{}", crspec ); - try (final KubernetesClient k8s = new KubernetesClientBuilder().build()) { + try { GenericKubernetesResource gkr = Serialization.unmarshal( crspec ); headers.forEach(((hname, hval) ->{ @@ -468,7 +468,7 @@ public class KubernetesClientResource { .withName( nameSpacename ) .addToLabels("org.etsi.osl", "org.etsi.osl") .endMetadata().build(); - k8s.namespaces().resource(ns).create(); + kubernetesClient.namespaces().resource(ns).create(); }catch (Exception e) { @@ -492,7 +492,7 @@ public class KubernetesClientResource { logger.debug("Object to deploy:{}", gkr.toString() ); - Resource<GenericKubernetesResource> dummyObject = k8s.resource( gkr ); + Resource<GenericKubernetesResource> dummyObject = kubernetesClient.resource( gkr ); GenericKubernetesResource result = dummyObject.create(); diff --git a/src/test/java/org/etsi/osl/cridge/CridgeIntegrationTest.java b/src/test/java/org/etsi/osl/cridge/CridgeIntegrationTest.java index 122aa3892f509861f38097b3811a89a01f6825a4..1e334e54b085eaf16a2baa416a0a58d6d2e21463 100644 --- a/src/test/java/org/etsi/osl/cridge/CridgeIntegrationTest.java +++ b/src/test/java/org/etsi/osl/cridge/CridgeIntegrationTest.java @@ -1,13 +1,27 @@ package org.etsi.osl.cridge; +import static io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext.v1CRDFromCustomResourceType; +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.net.HttpURLConnection; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.apache.camel.CamelContext; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.dataformat.JsonLibrary; +import org.apache.commons.io.IOUtils; import org.etsi.osl.tmf.ri639.model.ResourceCreate; import org.junit.Rule; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; @@ -23,22 +37,43 @@ import jakarta.annotation.PostConstruct; import org.springframework.test.context.event.RecordApplicationEvents; import org.springframework.test.context.event.annotation.BeforeTestClass; import org.springframework.test.context.junit4.SpringRunner; +import io.fabric8.kubernetes.api.model.Condition; +import io.fabric8.kubernetes.api.model.KubernetesResource; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.api.model.ListMeta; import io.fabric8.kubernetes.api.model.ListMetaBuilder; import io.fabric8.kubernetes.api.model.NamedContext; import io.fabric8.kubernetes.api.model.NamedContextBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.WatchEvent; +import io.fabric8.kubernetes.api.model.WatchEventBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionListBuilder; +import io.fabric8.kubernetes.api.model.resource.v1alpha2.ResourceHandle; +import io.fabric8.kubernetes.api.model.resource.v1alpha2.ResourceHandleBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.extension.ExtensionAdapter.ClientFactory; import io.fabric8.kubernetes.client.http.HttpClient.Factory; +import io.fabric8.kubernetes.client.impl.ResourceHandler; +import io.fabric8.kubernetes.client.informers.ResourceEventHandler; +import io.fabric8.kubernetes.client.informers.SharedIndexInformer; +import io.fabric8.kubernetes.client.informers.SharedInformerFactory; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesClientBuilderCustomizer; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import io.fabric8.kubernetes.client.utils.Serialization; +import io.fabric8.kubernetes.internal.KubernetesDeserializer; +import io.fabric8.kubernetes.client.impl.Handlers; @RecordApplicationEvents @RunWith(SpringRunner.class) @@ -57,6 +92,9 @@ public class CridgeIntegrationTest { private static KubernetesMockServer server; + @Autowired + KubernetesClientResource kubernetesClientResource; + @Autowired CatalogClient catalogClient; @@ -72,10 +110,10 @@ public class CridgeIntegrationTest { private SCMocked scmocked = new SCMocked(); - private KubernetesClient kubernetesClient; + private static KubernetesClient kubernetesClient; @Autowired - KubernetesClientResource aKubernetesClientResource; + KubernetesClientResource kubernetesClientResource; RoutesBuilder builder = new RouteBuilder() { @Override @@ -107,33 +145,28 @@ public class CridgeIntegrationTest { kubernetesClient.getConfiguration().setCurrentContext(nctx); - aKubernetesClientResource.setKubernetesClient(kubernetesClient); + kubernetesClientResource.setKubernetesClient(kubernetesClient); logger.info("Starting preparedForTheTest kubernetesClient.toString() {} ", kubernetesClient.toString()); logger.info("Starting preparedForTheTest for cluster getContexts {} ", kubernetesClient.getConfiguration().getContexts().toString()); - - ListMeta metada = new ListMetaBuilder().build(); + + ListMeta metada = new ListMetaBuilder().build(); // Given - CustomResourceDefinitionList list = new CustomResourceDefinitionListBuilder() - .withMetadata(metada ) - .build(); - - - //the following expects are for the sharedIndexInformers in the beginning - server.expect() - .get() - .withPath("/apis/apiextensions.k8s.io/v1/customresourcedefinitions?resourceVersion=0") - .andReturn(HttpURLConnection.HTTP_OK, list ) - .once(); - - server.expect() - .get() - .withPath("/apis/apiextensions.k8s.io/v1/customresourcedefinitions?allowWatchBookmarks=true&timeoutSeconds=600&watch=true") - .andReturn(HttpURLConnection.HTTP_OK, list ) - .once(); - + CustomResourceDefinitionList list = + new CustomResourceDefinitionListBuilder().withMetadata(metada).build(); + + + // the following expects are for the sharedIndexInformers in the beginning + server.expect().get() + .withPath("/apis/apiextensions.k8s.io/v1/customresourcedefinitions?resourceVersion=0") + .andReturn(HttpURLConnection.HTTP_OK, list).once(); + + server.expect().get().withPath( + "/apis/apiextensions.k8s.io/v1/customresourcedefinitions?allowWatchBookmarks=true&timeoutSeconds=600&watch=true") + .andReturn(HttpURLConnection.HTTP_OK, list).once(); + try { camelContext.addRoutes(builder); } catch (Exception e) { @@ -142,21 +175,176 @@ public class CridgeIntegrationTest { } } - @BeforeTestClass - public void beforeTestClass(ContextRefreshedEvent event) { - logger.info("=============== beforeTestClass ============================="); + + @Test + public void testCRDRegister() throws Exception { + + logger.info("===============TEST testCRDRegister ============================="); + CustomResourceDefinition crdMyCalc = v1CRDFromCustomResourceType(MyCalculator.class).build(); + server.expect().post().withPath("/apis/apiextensions.k8s.io/v1/customresourcedefinitions") + .andReturn(HttpURLConnection.HTTP_OK, crdMyCalc).once(); + // When + CustomResourceDefinition createdCronTabCrd = routesPreparation.kubernetesClient.apiextensions() + .v1().customResourceDefinitions().resource(crdMyCalc).create(); + + // Then + assertNotNull(createdCronTabCrd); + } + @Test + public void testCRDEPLOY() throws Exception { + logger.info("===============TEST testCRDEPLOY ============================="); + + + // CustomResourceDefinition cronTabCrd = routesPreparation.kubernetesClient.apiextensions().v1() + // .customResourceDefinitions() + // .load(new BufferedInputStream(new FileInputStream("src/test/resources/crontab-crd.yaml"))) + // .item(); + + + //prepare CR_SPEC request + CustomResourceDefinition crdMyCalc = v1CRDFromCustomResourceType(MyCalculator.class).build(); + + + + Map<String, Object> map = new HashMap<>(); + map.put("currentContextCluster", "testCluster"); + map.put("clusterMasterURL", server.getHostName()); + map.put("org.etsi.osl.serviceId", "sid-xxx-xxx-xxx"); + map.put("org.etsi.osl.resourceId", "rid-xxx-xxx-xxx"); + map.put("org.etsi.osl.prefixName", "crrid12345"); + map.put("org.etsi.osl.serviceOrderId", "orderid-xxx-xxx-xxx"); + map.put("org.etsi.osl.namespace", "orderid-xxx-xxx-xxx"); + map.put("org.etsi.osl.statusCheckFieldName", "_CR_CHECK_FIELD"); + map.put("org.etsi.osl.statusCheckValueStandby", "_CR_CHECKVAL_STANDBY"); + map.put("org.etsi.osl.statusCheckValueAlarm", "_CR_CHECKVAL_ALARM"); + map.put("org.etsi.osl.statusCheckValueAvailable", "_CR_CHECKVAL_AVAILABLE"); + map.put("org.etsi.osl.statusCheckValueReserved", "_CR_CHECKVAL_RESERVED"); + map.put("org.etsi.osl.statusCheckValueUnknown", "_CR_CHECKVAL_UNKNOWN"); + map.put("org.etsi.osl.statusCheckValueSuspended", "_CR_CHECKVAL_SUSPENDED"); + + + String _CR_SPEC = Serialization.asYaml(getMyCalculator("test-resource")); + + //First check for an invalid context cluster + String response = kubernetesClientResource.deployCR(map, _CR_SPEC); + assertEquals("SEE OTHER", response); + + + //Now try for the correct context cluster + map.put("currentContextCluster", + kubernetesClientResource.getKubernetesContextDefinition().getCurrentContextCluster()); + map.put("clusterMasterURL", + kubernetesClientResource.getKubernetesContextDefinition().getMasterURL()); + + + // server api expectations + server.expect().get() + .withPath("/api/v1/namespaces/orderid-xxx-xxx-xxx/secrets?resourceVersion=0") + .andReturn(HttpURLConnection.HTTP_OK, crdMyCalc).once(); + + server.expect().get().withPath( + "/api/v1/namespaces/orderid-xxx-xxx-xxx/secrets?allowWatchBookmarks=true&timeoutSeconds=600&watch=true") + .andReturn(HttpURLConnection.HTTP_OK, crdMyCalc).once(); + + server.expect().get() + .withPath("/apis/stable.example.com/v1/namespaces/orderid-xxx-xxx-xxx/mycalculators") + .andReturn(HttpURLConnection.HTTP_OK, crdMyCalc).once(); + + server.expect().get().withPath( + "/apis/stable.example.com/v1/namespaces/orderid-xxx-xxx-xxx/mycalculators/amycalculator") + .andReturn(HttpURLConnection.HTTP_OK, crdMyCalc).once(); + + server.expect().post().withPath("/api/v1/namespaces/orderid-xxx-xxx-xxx/mycalculators") + .andReturn(HttpURLConnection.HTTP_CREATED, crdMyCalc).once(); + + server.expect().post() + .withPath( + "/apis/examples.osl.etsi.org/v1alpha1/namespaces/orderid-xxx-xxx-xxx/mycalculators") + .andReturn(HttpURLConnection.HTTP_CREATED, crdMyCalc).once(); + + //register the resource handler + KubernetesClient client = routesPreparation.kubernetesClient; + + client.getKubernetesSerialization().registerKubernetesResource("examples.osl.etsi.org/v1alpha1", + "MyCalculator", MyCalculator.class); + + //make the deployment test! + response = kubernetesClientResource.deployCR(map, _CR_SPEC); + + assertEquals("OK", response); } - @Test - public void testCatalog() throws Exception { - logger.info("===============TEST testCatalog ============================="); + public void testCRWithWatch() throws Exception { + // useful blog + // https://itnext.io/mock-kubernetes-api-server-in-java-using-fabric8-kubernetes-mock-server-81a75cf6c47c + + // Given + KubernetesClient client = routesPreparation.kubernetesClient; + + server.expect().withPath( + "/apis/examples.osl.etsi.org/v1alpha1/namespaces/orderid-xxx-xxx-xxx/mycalculators?allowWatchBookmarks=true&watch=true") + .andUpgradeToWebSocket().open().waitFor(10L) + .andEmit(new WatchEvent(getMyCalculator("amycalculator"), "ADDED")).waitFor(20L) + .andEmit(new WatchEventBuilder().withNewStatusObject() + .withMessage("410 - the event requested is outdated") + .withCode(HttpURLConnection.HTTP_GONE).endStatusObject().build()) + .done().always(); + + MixedOperation<MyCalculator, KubernetesResourceList<MyCalculator>, Resource<MyCalculator>> userAclClient = + client.resources(MyCalculator.class); + + + // When + CountDownLatch eventRecieved = new CountDownLatch(1); + client.getKubernetesSerialization().registerKubernetesResource("examples.osl.etsi.org/v1alpha1", + "MyCalculator", MyCalculator.class); + + Watch watch = + userAclClient.inNamespace("orderid-xxx-xxx-xxx").watch(new Watcher<MyCalculator>() { + @Override + public void eventReceived(Action action, MyCalculator calc) { + if (action.name().contains("ADDED")) { + eventRecieved.countDown(); + logger.info( + "===============CountDownLatch eventRecieved.countDown ADDED {}=============================", + Serialization.asYaml(calc)); + } + } + + @Override + public void onClose(WatcherException e) {} + }); + + // Then + eventRecieved.await(30, TimeUnit.SECONDS); + Assertions.assertEquals(0, eventRecieved.getCount()); + watch.close(); + + } + + + private KubernetesResource getMyCalculator(String resourceName) { + MyCalculatorSpec spec = new MyCalculatorSpec(); + spec.setAction("amycalculator"); + MyCalculator createdMyCalcL = new MyCalculator(); + createdMyCalcL.setMetadata(new ObjectMetaBuilder().withName(resourceName).build()); + createdMyCalcL.setSpec(spec); + Condition condition = new Condition(); + condition.setMessage("Last reconciliation succeeded"); + condition.setReason("Successful"); + condition.setStatus("True"); + condition.setType("Successful"); + MyCalculatorStatus status = new MyCalculatorStatus(); + status.setCondition(new Condition[] {condition}); + createdMyCalcL.setStatus(status); + return createdMyCalcL; } diff --git a/src/test/java/org/etsi/osl/cridge/MyCalculator.java b/src/test/java/org/etsi/osl/cridge/MyCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..cbadd3e9bb37482bc93a1382e3c0f8a1e9fb82a1 --- /dev/null +++ b/src/test/java/org/etsi/osl/cridge/MyCalculator.java @@ -0,0 +1,13 @@ +package org.etsi.osl.cridge; + +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("v1alpha1") // -> CRD Version +@Group("examples.osl.etsi.org") // -> CRD Group +public class MyCalculator // -> CRD Kind (if not provided in @Kind annotation) + extends CustomResource<MyCalculatorSpec, MyCalculatorStatus> + implements Namespaced { +} // -> CRD scope Namespaced \ No newline at end of file diff --git a/src/test/java/org/etsi/osl/cridge/MyCalculatorSpec.java b/src/test/java/org/etsi/osl/cridge/MyCalculatorSpec.java new file mode 100644 index 0000000000000000000000000000000000000000..e1be74eb2bddc75f567fb464034638122a19a4d2 --- /dev/null +++ b/src/test/java/org/etsi/osl/cridge/MyCalculatorSpec.java @@ -0,0 +1,47 @@ +package org.etsi.osl.cridge; + + +public class MyCalculatorSpec { + private int parama; + private int paramb; + private String action; + /** + * @return the parama + */ + public int getParama() { + return parama; + } + /** + * @param parama the parama to set + */ + public void setParama(int parama) { + this.parama = parama; + } + /** + * @return the paramb + */ + public int getParamb() { + return paramb; + } + /** + * @param paramb the paramb to set + */ + public void setParamb(int paramb) { + this.paramb = paramb; + } + /** + * @return the action + */ + public String getAction() { + return action; + } + /** + * @param action the action to set + */ + public void setAction(String action) { + this.action = action; + } + + + +} \ No newline at end of file diff --git a/src/test/java/org/etsi/osl/cridge/MyCalculatorStatus.java b/src/test/java/org/etsi/osl/cridge/MyCalculatorStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..30905fa89cd189e5c926d9eec7f465751e0b91c0 --- /dev/null +++ b/src/test/java/org/etsi/osl/cridge/MyCalculatorStatus.java @@ -0,0 +1,54 @@ +package org.etsi.osl.cridge; + +import io.fabric8.kubernetes.api.model.Condition; + +public class MyCalculatorStatus { + private int result; + private String status; + Condition[] condition; + + /** + * @return the result + */ + public int getResult() { + return result; + } + + /** + * @param result the result to set + */ + public void setResult(int result) { + this.result = result; + } + + /** + * @return the status + */ + public String getStatus() { + return status; + } + + /** + * @param status the status to set + */ + public void setStatus(String status) { + this.status = status; + } + + /** + * @return the condition + */ + public Condition[] getCondition() { + return condition; + } + + /** + * @param condition the condition to set + */ + public void setCondition(Condition[] condition) { + this.condition = condition; + } + + + +} diff --git a/src/test/resources/crontab-cr.yaml b/src/test/resources/crontab-cr.yaml new file mode 100644 index 0000000000000000000000000000000000000000..88fb77d745dd17d878279b88326fed71f8d5c656 --- /dev/null +++ b/src/test/resources/crontab-cr.yaml @@ -0,0 +1,9 @@ +# Taken from https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#create-custom-objects + +apiVersion: "stable.example.com/v1" +kind: CronTab +metadata: + name: my-new-cron-object +spec: + cronSpec: "* * * * */5" + image: my-awesome-cron-image \ No newline at end of file