CBS lookup algorithm 87/79187/7
authorPiotr Jaszczyk <piotr.jaszczyk@nokia.com>
Tue, 26 Feb 2019 14:08:37 +0000 (15:08 +0100)
committerPiotr Jaszczyk <piotr.jaszczyk@nokia.com>
Wed, 27 Feb 2019 07:09:11 +0000 (08:09 +0100)
Implement CBS lookup algorithm as docummented on
https://wiki.onap.org/display/DW/MicroServices+Onboarding+in+ONAP#MicroServicesOnboardinginONAP-CodesnippettofetchconfigurationfromConfigbindingservice

Change-Id: Ib465c5fd44b853ba46e152259744dbdd775872a0
Issue-ID: DCAEGEN2-1233
Signed-off-by: Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
13 files changed:
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/CbsClient.java
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/CbsClientFactory.java
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/EnvProperties.java [moved from rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/http/configuration/EnvProperties.java with 51% similarity]
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsClientImpl.java
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookup.java [new file with mode: 0644]
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/adapters/CloudHttpClient.java [moved from rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/http/configuration/CloudHttpClient.java with 89% similarity]
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/providers/CloudConfigurationClient.java
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/providers/CloudConfigurationProvider.java
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/providers/ReactiveCloudConfigurationProvider.java
rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/EnvPropertiesTest.java [new file with mode: 0644]
rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookupTest.java [new file with mode: 0644]
rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/providers/ReactiveCloudConfigurationProviderTest.java
rest-services/cbs-client/src/test/resources/consul_cbs_service.json [new file with mode: 0644]

index 0f50fca..7378926 100644 (file)
  */
 package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api;
 
+import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import java.time.Duration;
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import org.jetbrains.annotations.NotNull;
 
@@ -37,9 +40,24 @@ public interface CbsClient {
      * <p>
      * Returns a {@link Mono} that publishes new configuration after CBS client retrieves one.
      *
-     * @param serviceComponentName url key under which CBS client should look for configuration
      * @return reactive stream of configuration
      * @since 1.1.2
      */
-    @NotNull Mono<JsonObject> get(String serviceComponentName);
+    @NotNull Mono<JsonObject> get();
+
+
+    /**
+     * Poll for configuration.
+     *
+     * Will call {@link #get()} after {@code initialDelay} every {@code period}. Resulting entries may or may not be
+     * changed, ie. items in the stream might be the same until change is made in CBS.
+     *
+     * @param initialDelay delay after first request attempt
+     * @param period frequency of update checks
+     * @return stream of configuration states
+     */
+    default Flux<JsonElement> get(Duration initialDelay, Duration period) {
+        return Flux.interval(initialDelay, period)
+                .flatMap(i -> get());
+    }
 }
index f81cd6b..c1b1434 100644 (file)
@@ -21,6 +21,8 @@ package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api;
 
 import org.jetbrains.annotations.NotNull;
 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.CbsClientImpl;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.CbsLookup;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
 import reactor.core.publisher.Mono;
 
 /**
@@ -40,11 +42,16 @@ public class CbsClientFactory {
      * client configured with found address. Created client will be published in returned Mono instance.
      * </p>
      *
+     * @param env required environment properties
      * @return non-null {@link Mono} of {@link CbsClient} instance
      * @since 1.1.2
      */
-    @NotNull
-    public static Mono<CbsClient> createCbsClient() {
-        return Mono.just(new CbsClientImpl());
+    public static @NotNull Mono<CbsClient> createCbsClient(EnvProperties env) {
+        return Mono.defer(() -> {
+            final CloudHttpClient httpClient = new CloudHttpClient();
+            final CbsLookup lookup = new CbsLookup(httpClient);
+            return lookup.lookup(env)
+                    .map(addr -> new CbsClientImpl(httpClient, env.appName()));
+        });
     }
 }
@@ -1,24 +1,24 @@
 /*
- * ============LICENSE_START=======================================================
+ * ============LICENSE_START====================================
  * DCAEGEN2-SERVICES-SDK
- * ================================================================================
- * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved.
- * ================================================================================
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- * ============LICENSE_END=========================================================
+ * ============LICENSE_END=====================================
  */
 
-package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration;
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api;
 
 import org.immutables.value.Value;
 
@@ -34,6 +34,22 @@ import org.immutables.value.Value;
 @Value.Immutable(prehash = true)
 public interface EnvProperties {
 
+    /**
+     * Name of environment variable containing Consul host name.
+     */
+    String ENV_CONSUL_HOST = "CONSUL_HOST";
+
+    /**
+     * Name of environment variable containing Config Binding Service <em>service name</em> as registered in Consul
+     * services API.
+     */
+    String ENV_CBS_NAME = "CONFIG_BINDING_SERVICE";
+
+    /**
+     * Name of environment variable containing current application name.
+     */
+    String ENV_APP_NAME = "HOSTNAME";
+
     @Value.Parameter
     String consulHost();
 
@@ -45,4 +61,19 @@ public interface EnvProperties {
 
     @Value.Parameter
     String appName();
+
+    /**
+     * Creates EnvProperties from system environment variables.
+     *
+     * @return an instance of EnvProperties
+     * @throws NullPointerException when at least one of required parameters is missing
+     */
+    static EnvProperties fromEnvironment() {
+        return ImmutableEnvProperties.builder()
+                .consulHost(System.getenv(ENV_CONSUL_HOST))
+                .consulPort(8050)
+                .cbsName(System.getenv(ENV_CBS_NAME))
+                .appName(System.getenv(ENV_APP_NAME))
+                .build();
+    }
 }
index 7bd80ed..1df42c6 100644 (file)
@@ -22,13 +22,21 @@ package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl;
 import com.google.gson.JsonObject;
 import org.jetbrains.annotations.NotNull;
 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
 import reactor.core.publisher.Mono;
 
 public class CbsClientImpl implements CbsClient {
+    private final CloudHttpClient httpClient;
+    private final String serviceName;
+
+    public CbsClientImpl(
+            CloudHttpClient httpClient, String serviceName) {
+        this.httpClient = httpClient;
+        this.serviceName = serviceName;
+    }
 
-    @NotNull
     @Override
-    public Mono<JsonObject> get(String serviceComponentName) {
+    public @NotNull Mono<JsonObject> get() {
         return Mono.empty();
     }
 }
diff --git a/rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookup.java b/rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookup.java
new file mode 100644 (file)
index 0000000..ca7058f
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.net.InetSocketAddress;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since February 2019
+ */
+public class CbsLookup {
+
+    private static final String CONSUL_JSON_SERVICE_ADDRESS = "ServiceAddress";
+    private static final String CONSUL_JSON_SERVICE_PORT = "ServicePort";
+    private final CloudHttpClient httpClient;
+
+    public CbsLookup(CloudHttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    public Mono<InetSocketAddress> lookup(EnvProperties env) {
+        return Mono.fromCallable(() -> createConsulUrl(env))
+                .flatMap(this::fetchHttpData)
+                .flatMap(this::firstService)
+                .map(this::parseServiceEntry);
+    }
+
+    private String createConsulUrl(EnvProperties env) {
+        return String.format("http://%s:%s/v1/catalog/service/%s", env.consulHost(), env.consulPort(), env.cbsName());
+    }
+
+    private Mono<JsonArray> fetchHttpData(String consulUrl) {
+        return httpClient.callHttpGet(consulUrl, JsonArray.class);
+    }
+
+    private Mono<JsonObject> firstService(JsonArray services) {
+        return services.size() == 0
+                ? Mono.empty()
+                : Mono.just(services.get(0).getAsJsonObject());
+    }
+
+    private InetSocketAddress parseServiceEntry(JsonObject service) {
+        return InetSocketAddress.createUnresolved(
+                service.get(CONSUL_JSON_SERVICE_ADDRESS).getAsString(),
+                service.get(CONSUL_JSON_SERVICE_PORT).getAsInt());
+    }
+
+}
+
@@ -1,24 +1,24 @@
 /*
- * ============LICENSE_START=======================================================
+ * ============LICENSE_START====================================
  * DCAEGEN2-SERVICES-SDK
- * ================================================================================
- * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved.
- * ================================================================================
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- * ============LICENSE_END=========================================================
+ * ============LICENSE_END=====================================
  */
 
-package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration;
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
@@ -31,7 +31,6 @@ import reactor.netty.http.client.HttpClient;
 import reactor.netty.http.client.HttpClientRequest;
 import reactor.netty.http.client.HttpClientResponse;
 
-
 /**
  * @author <a href="mailto:przemyslaw.wasala@nokia.com">Przemysław Wąsala</a> on 11/15/18
  */
index 594db6d..8a1abe3 100644 (file)
@@ -22,8 +22,8 @@
 package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.providers;
 
 import com.google.gson.JsonObject;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.EnvProperties;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.ImmutableEnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.ImmutableEnvProperties;
 import reactor.core.publisher.Mono;
 
 /**
index f8486f6..e110534 100644 (file)
@@ -22,7 +22,7 @@
 package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.providers;
 
 import com.google.gson.JsonObject;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.EnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
 import reactor.core.publisher.Mono;
 
 /**
index 88665e7..5606a2d 100644 (file)
@@ -23,8 +23,8 @@ package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.providers;
 
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.CloudHttpClient;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.EnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
 import org.onap.dcaegen2.services.sdk.rest.services.uri.URI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/EnvPropertiesTest.java b/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/EnvPropertiesTest.java
new file mode 100644 (file)
index 0000000..b48543d
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api;
+
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since February 2019
+ */
+class EnvPropertiesTest {
+    @Test
+    void fromEnvironmentShouldFailWhenEnvVariablesAreMissing() {
+        assertThatExceptionOfType(NullPointerException.class).isThrownBy(EnvProperties::fromEnvironment);
+    }
+}
\ No newline at end of file
diff --git a/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookupTest.java b/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/impl/CbsLookupTest.java
new file mode 100644 (file)
index 0000000..e751385
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
+import org.junit.jupiter.api.Test;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.ImmutableEnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since February 2019
+ */
+class CbsLookupTest {
+
+    private final EnvProperties env = ImmutableEnvProperties.builder()
+            .cbsName("cbs-service")
+            .consulHost("consul.local")
+            .consulPort(8050)
+            .appName("whatever").build();
+    private final CloudHttpClient httpClient = mock(CloudHttpClient.class);
+    private final CbsLookup cut = new CbsLookup(httpClient);
+
+    @Test
+    void lookupShouldReturnValidConfiguration() {
+        // given
+        givenConsulResponse(parseResource("/consul_cbs_service.json").getAsJsonArray());
+
+        // when
+        final InetSocketAddress result = cut.lookup(env).block();
+
+        // then
+        assertThat(result.getHostString()).isEqualTo("config-binding-service");
+        assertThat(result.getPort()).isEqualTo(10000);
+    }
+
+    @Test
+    void lookupShouldReturnEmptyResultWhenServiceArrayIsEmpty() {
+        // given
+        givenConsulResponse(new JsonArray());
+
+        // when
+        final Mono<InetSocketAddress> result = cut.lookup(env);
+
+        // then
+        StepVerifier.create(result).verifyComplete();
+    }
+
+    private JsonElement parseResource(String resource) {
+        return new JsonParser().parse(new InputStreamReader(CbsLookupTest.class.getResourceAsStream(resource)));
+    }
+
+    private void givenConsulResponse(JsonArray jsonArray) {
+        final String url = "http://"
+                + env.consulHost()
+                + ":"
+                + env.consulPort()
+                + "/v1/catalog/service/"
+                + env.cbsName();
+        given(httpClient.callHttpGet(url, JsonArray.class))
+                .willReturn(Mono.just(jsonArray));
+    }
+
+}
\ No newline at end of file
index 5d6ef6b..4e8782b 100644 (file)
@@ -27,9 +27,9 @@ import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import org.junit.jupiter.api.Test;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.CloudHttpClient;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.EnvProperties;
-import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.http.configuration.ImmutableEnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.ImmutableEnvProperties;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.impl.adapters.CloudHttpClient;
+import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.EnvProperties;
 import reactor.core.publisher.Mono;
 import reactor.test.StepVerifier;
 
diff --git a/rest-services/cbs-client/src/test/resources/consul_cbs_service.json b/rest-services/cbs-client/src/test/resources/consul_cbs_service.json
new file mode 100644 (file)
index 0000000..49b1df2
--- /dev/null
@@ -0,0 +1,23 @@
+[
+    {
+        "Address": "10.42.102.75",
+        "CreateIndex": 1097,
+        "Datacenter": "dc1",
+        "ID": "2a69de1b-4c2d-a958-5058-639acc926fde",
+        "ModifyIndex": 1097,
+        "Node": "dcae-bootstrap",
+        "NodeMeta": {
+            "consul-network-segment": ""
+        },
+        "ServiceAddress": "config-binding-service",
+        "ServiceEnableTagOverride": false,
+        "ServiceID": "dcae-cbs0",
+        "ServiceName": "config_binding_service",
+        "ServicePort": 10000,
+        "ServiceTags": [],
+        "TaggedAddresses": {
+            "lan": "10.42.102.75",
+            "wan": "10.42.102.75"
+        }
+    }
+]