Add multi-layer RIC instance selector 33/2333/3
authorjh245g <jh245g@att.com>
Fri, 24 Jan 2020 15:54:54 +0000 (10:54 -0500)
committerLott, Christopher (cl778h) <cl778h@att.com>
Fri, 24 Jan 2020 19:50:36 +0000 (14:50 -0500)
Extend for config data that provides a list of regions,
each of which contains a list of RIC instances.

Change-Id: I61a7b15e20e49d661b7539ba52c3cafa5c44dee2
Signed-off-by: Jun (Nicolas) Hu <jh245g@att.com>
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
31 files changed:
docs/release-notes.rst
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1MediatorApiBuilder.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1MediatorConfiguration.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AppManagerApiBuilder.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AppManagerConfiguration.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/CaasIngressConfiguration.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/E2ManagerApiBuilder.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/E2ManagerConfiguration.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/RICInstanceConfiguration.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SimpleKubernetesClientBuilder.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AdminController.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/AppTransport.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RanDetailsTransport.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicInstance.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicInstanceKeyName.java
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegion.java [new file with mode: 0644]
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegionList.java [moved from webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicInstanceList.java with 54% similarity]
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegionTransport.java [new file with mode: 0644]
webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java
webapp-backend/src/main/resources/application.yaml
webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/RICInstanceMockConfiguration.java
webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/test/AbstractConfigTest.java
webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/model/ModelTest.java
webapp-frontend/src/app/interfaces/dashboard.types.ts
webapp-frontend/src/app/services/instance-selector/instance-selector.service.ts
webapp-frontend/src/app/ui/instance-selector-dialog/instance-selector-dialog.component.html
webapp-frontend/src/app/ui/instance-selector-dialog/instance-selector-dialog.component.ts

index 87fe784..7da3ca4 100644 (file)
@@ -5,7 +5,7 @@
 RIC Dashboard Release Notes
 ===========================
 
-Version 2.0.0, 20 Jan 2020
+Version 2.0.0, 24 Jan 2020
 --------------------------
 * Change application properties file to YAML format
 * Add mock list of RIC instances to application properties
@@ -21,6 +21,7 @@ Version 2.0.0, 20 Jan 2020
 * Upgrade E2 Manager API and submodule to tag 3.0.3
 * Add instance selector dialog component and service
 * Repair onSidenavClose method
+* Extend to support selecting a RIC instance within a region
 
 Version 1.3.0, 26 Nov 2019
 --------------------------
index 3a6d6a2..136606b 100644 (file)
@@ -24,7 +24,8 @@ import java.lang.invoke.MethodHandles;
 import java.util.List;
 
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegion;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,7 +43,7 @@ public class DashboardApplication implements CommandLineRunner {
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
        @Autowired
-       private RicInstanceList instanceConfig;
+       private RicRegionList instanceConfig;
 
        public static void main(String[] args) {
                SpringApplication.run(DashboardApplication.class, args);
@@ -53,15 +54,18 @@ public class DashboardApplication implements CommandLineRunner {
                // Ensure output appears on the console by using level WARN
                logger.warn("run: version '{}'", getImplementationVersion(MethodHandles.lookup().lookupClass()));
                // Validate configuration
-               List<RicInstance> instances = instanceConfig.getInstances();
-               Assert.notEmpty(instances, "Instance list empty");
-               for (RicInstance it : instances) {
-                       logger.warn("run: RIC instance {}", it);
-                       Assert.hasText(it.getKey(), "Instance key missing");
-                       Assert.hasText(it.getName(), "Name missing for instance " + it.getKey());
-                       Assert.hasText(it.getAppUrlPrefix(), "App URL prefix missing for instance " + it.getKey());
-                       Assert.hasText(it.getCaasUrlPrefix(), "Caas URL prefix missing for instance " + it.getKey());
-                       Assert.hasText(it.getPltUrlPrefix(), "Plt URL prefix missing for instance " + it.getKey());
+               List<RicRegion> regions = instanceConfig.getRegions();
+               Assert.notEmpty(regions, "Region list empty");
+               for (RicRegion r : regions) {
+                       Assert.notEmpty(r.getInstances(), "Instance list empty for region " + r.getName());
+                       for (RicInstance it : r.getInstances()) {
+                               logger.warn("run: RIC region {} instance {}", r, it);
+                               Assert.hasText(it.getKey(), "Instance key missing");
+                               Assert.hasText(it.getName(), "Name missing for instance " + it.getKey());
+                               Assert.hasText(it.getAppUrlPrefix(), "App URL prefix missing for instance " + it.getKey());
+                               Assert.hasText(it.getCaasUrlPrefix(), "Caas URL prefix missing for instance " + it.getKey());
+                               Assert.hasText(it.getPltUrlPrefix(), "Plt URL prefix missing for instance " + it.getKey());
+                       }
                }
        }
 
index 06fccc1..21f3033 100644 (file)
@@ -24,7 +24,7 @@ import java.lang.invoke.MethodHandles;
 import org.oransc.ric.a1med.client.api.A1MediatorApi;
 import org.oransc.ric.a1med.client.invoker.ApiClient;
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.client.RestTemplate;
@@ -42,9 +42,9 @@ public class A1MediatorApiBuilder {
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
-       public A1MediatorApiBuilder(final RicInstanceList instanceConfig, final String urlSuffix) {
+       public A1MediatorApiBuilder(final RicRegionList instanceConfig, final String urlSuffix) {
                logger.debug("ctor: suffix {}", urlSuffix);
                this.instanceConfig = instanceConfig;
                this.urlSuffix = urlSuffix;
index 6853806..2e8efb2 100644 (file)
@@ -21,7 +21,7 @@ package org.oransc.ric.portal.dashboard.config;
 
 import java.lang.invoke.MethodHandles;
 
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,11 +42,11 @@ public class A1MediatorConfiguration {
 
        // Populated by the autowired constructor
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
        @Autowired
        public A1MediatorConfiguration(@Value("${a1med.url.suffix}") final String urlSuffix,
-                       final RicInstanceList instanceConfig) {
+                       final RicRegionList instanceConfig) {
                logger.info("ctor: URL suffix {}", urlSuffix);
                this.urlSuffix = urlSuffix;
                this.instanceConfig = instanceConfig;
index 5081ec8..c92baa7 100644 (file)
@@ -25,7 +25,7 @@ import org.oransc.ric.plt.appmgr.client.api.HealthApi;
 import org.oransc.ric.plt.appmgr.client.api.XappApi;
 import org.oransc.ric.plt.appmgr.client.invoker.ApiClient;
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.client.RestTemplate;
@@ -43,9 +43,9 @@ public class AppManagerApiBuilder {
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
-       public AppManagerApiBuilder(final RicInstanceList instanceConfig, final String urlSuffix) {
+       public AppManagerApiBuilder(final RicRegionList instanceConfig, final String urlSuffix) {
                logger.debug("ctor: suffix {}", urlSuffix);
                this.instanceConfig = instanceConfig;
                this.urlSuffix = urlSuffix;
index ffb5db6..e4e3751 100644 (file)
@@ -21,7 +21,7 @@ package org.oransc.ric.portal.dashboard.config;
 
 import java.lang.invoke.MethodHandles;
 
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,11 +42,11 @@ public class AppManagerConfiguration {
 
        // Populated by the autowired constructor
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
        @Autowired
        public AppManagerConfiguration(@Value("${appmgr.url.suffix}") final String urlSuffix,
-                       final RicInstanceList instanceConfig) {
+                       final RicRegionList instanceConfig) {
                logger.info("ctor: URL suffix {}", urlSuffix);
                this.urlSuffix = urlSuffix;
                this.instanceConfig = instanceConfig;
index 7bb3149..648a847 100644 (file)
@@ -23,7 +23,7 @@ import java.lang.invoke.MethodHandles;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.oransc.ric.portal.dashboard.util.HttpsURLConnectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,13 +44,13 @@ public class CaasIngressConfiguration {
 
        // Populated by the autowired constructor
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
        @Autowired
        public CaasIngressConfiguration( //
                        @Value("${caasingress.plt.url.suffix}") final String pltUrlSuffix, //
                        @Value("${caasingress.insecure}") final Boolean insecureFlag, //
-                       final RicInstanceList instanceConfig) throws KeyManagementException, NoSuchAlgorithmException {
+                       final RicRegionList instanceConfig) throws KeyManagementException, NoSuchAlgorithmException {
                logger.debug("ctor: suffix {} insecure flag {}", pltUrlSuffix, insecureFlag);
                this.urlSuffix = pltUrlSuffix;
                this.instanceConfig = instanceConfig;
index 858a51b..c029184 100644 (file)
@@ -25,7 +25,7 @@ import org.oransc.ric.e2mgr.client.api.HealthCheckApi;
 import org.oransc.ric.e2mgr.client.api.NodebApi;
 import org.oransc.ric.e2mgr.client.invoker.ApiClient;
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.client.RestTemplate;
@@ -43,9 +43,9 @@ public class E2ManagerApiBuilder {
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
-       public E2ManagerApiBuilder(final RicInstanceList instanceConfig, final String urlSuffix) {
+       public E2ManagerApiBuilder(final RicRegionList instanceConfig, final String urlSuffix) {
                logger.debug("ctor: suffix {}", urlSuffix);
                this.instanceConfig = instanceConfig;
                this.urlSuffix = urlSuffix;
index 4b25622..ef9ab45 100644 (file)
@@ -21,7 +21,7 @@ package org.oransc.ric.portal.dashboard.config;
 
 import java.lang.invoke.MethodHandles;
 
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,11 +42,11 @@ public class E2ManagerConfiguration {
 
        // Populated by the autowired constructor
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
        @Autowired
        public E2ManagerConfiguration(@Value("${e2mgr.url.suffix}") final String urlSuffix,
-                       final RicInstanceList instanceConfig) {
+                       final RicRegionList instanceConfig) {
                logger.info("ctor: URL suffix {}", urlSuffix);
                this.urlSuffix = urlSuffix;
                this.instanceConfig = instanceConfig;
index 6159e4d..0e34bcf 100644 (file)
 
 package org.oransc.ric.portal.dashboard.config;
 
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegion;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -35,16 +38,26 @@ import org.springframework.context.annotation.Profile;
  * in application properties like this:
  * 
  * <pre>
- * ricinstances:
-    instances:
-        -
-          key: key1
-          name: Friendly Name One
-          urlPrefix: http://foo.bar.one/
-        -
-          key: key2
-          name: Friendly Name Two
-          urlPrefix: http://foo.bar.two/
+  ricinstances:
+    regions:
+      -
+        name: Region ABC
+        instances:
+            -
+              key: key1
+              name: Friendly Name One
+              appUrlPrefix: http://foo.bar.one/
+            -
+              key: key2
+              name: Friendly Name Two
+              appUrlPrefix: http://foo.bar.two/
+      -
+        name: Region DEF
+        instances:
+            -
+              key: key3
+              name: Friendly Name Three
+              appUrlPrefix: http://foo.bar.three/
  * </pre>
  */
 @Configuration
@@ -52,16 +65,19 @@ import org.springframework.context.annotation.Profile;
 @Profile("!test")
 public class RICInstanceConfiguration {
 
-       private List<RicInstance> instances = new ArrayList<>();
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       private List<RicRegion> regions = new ArrayList<>();
 
        // Called by spring with config data
-       public void setInstances(List<RicInstance> instances) {
-               this.instances = instances;
+       public void setRegions(List<RicRegion> regions) {
+               this.regions = regions;
        }
 
        @Bean
-       public RicInstanceList ricInstanceList() {
-               return new RicInstanceList(instances);
+       public RicRegionList ricRegions() {
+               logger.debug("Creating bean ricRegions");
+               return new RicRegionList(regions);
        }
 
 }
index 50af74f..84392c7 100644 (file)
@@ -25,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import org.oransc.ric.portal.dashboard.k8sapi.SimpleKubernetesClient;
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.util.DefaultUriBuilderFactory;
@@ -40,9 +40,9 @@ public class SimpleKubernetesClientBuilder {
        private Map<String, SimpleKubernetesClient> cache = new ConcurrentHashMap<>();
 
        private final String urlSuffix;
-       private final RicInstanceList instanceConfig;
+       private final RicRegionList instanceConfig;
 
-       public SimpleKubernetesClientBuilder(final RicInstanceList instanceConfig, final String urlSuffix) {
+       public SimpleKubernetesClientBuilder(final RicRegionList instanceConfig, final String urlSuffix) {
                logger.debug("ctor: suffix {}", urlSuffix);
                this.instanceConfig = instanceConfig;
                this.urlSuffix = urlSuffix;
index ccdf1bc..ce52f7a 100644 (file)
@@ -30,8 +30,9 @@ import org.oransc.ric.portal.dashboard.DashboardConstants;
 import org.oransc.ric.portal.dashboard.DashboardUserManager;
 import org.oransc.ric.portal.dashboard.model.ErrorTransport;
 import org.oransc.ric.portal.dashboard.model.IDashboardResponse;
-import org.oransc.ric.portal.dashboard.model.RicInstanceKeyName;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegion;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
+import org.oransc.ric.portal.dashboard.model.RicRegionTransport;
 import org.oransc.ric.portal.dashboard.model.SuccessTransport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -73,7 +74,7 @@ public class AdminController {
        private DashboardUserManager dashboardUserManager;
 
        @Autowired
-       private RicInstanceList instanceConfig;
+       private RicRegionList instanceConfig;
 
        @ApiOperation(value = "Gets the Dashboard MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
        @GetMapping(VERSION_METHOD)
@@ -102,12 +103,12 @@ public class AdminController {
                return dashboardUserManager.getUsers();
        }
 
-       @ApiOperation(value = "Gets the list of RIC instances.", response = RicInstanceKeyName.class, responseContainer = "List")
+       @ApiOperation(value = "Gets the RIC regions and instances.", response = RicRegion.class, responseContainer = "List")
        @GetMapping(INSTANCE_METHOD)
        @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
-       public List<RicInstanceKeyName> getInstances() {
-               logger.debug("getInstances");
-               return instanceConfig.getKeyNameList();
+       public List<RicRegionTransport> getRegionsInstances() {
+               logger.debug("getRegionsInstances");
+               return instanceConfig.getSimpleInstances();
        }
 
        @ApiOperation(value = "Gets the kibana metrics URL for the specified app.", response = SuccessTransport.class)
index abe026b..68203f2 100644 (file)
@@ -64,7 +64,7 @@ public class AppTransport implements IDashboardResponse {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[name=" + getName() + ", version=" + getVersion() + "]";
+               return this.getClass().getSimpleName() + "[name=" + getName() + ", version=" + getVersion() + "]";
        }
 
 }
index 30cab78..22e1a4a 100644 (file)
@@ -85,8 +85,8 @@ public class EcompUserDetails implements UserDetails {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[ecompUser=" + ecompUser + ", isAccountNonExpired=" + isAccountNonExpired()
-                               + ", isAccountNonLocked=" + isAccountNonLocked() + ", isCredentialsNonExpired="
+               return this.getClass().getSimpleName() + "[ecompUser=" + ecompUser + ", isAccountNonExpired="
+                               + isAccountNonExpired() + ", isAccountNonLocked=" + isAccountNonLocked() + ", isCredentialsNonExpired="
                                + isCredentialsNonExpired() + "]";
        }
 
index df5c3e4..2a90fca 100644 (file)
@@ -132,7 +132,7 @@ public class ErrorTransport implements IDashboardResponse {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[timestamp=" + getTimestamp() + ", error=" + getError() + ", path="
+               return this.getClass().getSimpleName() + "[timestamp=" + getTimestamp() + ", error=" + getError() + ", path="
                                + getPath() + "]";
        }
 
index 130a1c5..bb581aa 100644 (file)
@@ -63,8 +63,8 @@ public class RanDetailsTransport implements IDashboardResponse {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[nodebIdentity=" + getNodebIdentity() + ", nodebStatus=" + getNodebStatus()
-                               + "]";
+               return this.getClass().getSimpleName() + "[nodebIdentity=" + getNodebIdentity() + ", nodebStatus="
+                               + getNodebStatus() + "]";
        }
 
 }
index ccb224d..780bffc 100644 (file)
@@ -94,8 +94,8 @@ public class RicInstance extends RicInstanceKeyName {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[key=" + getKey() + ", name=" + getName() + ", appUrlPrefix=" + appUrlPrefix
-                               + ", pltUrlPrefix=" + pltUrlPrefix + ", caasUrlPrefix=" + caasUrlPrefix + "]";
+               return this.getClass().getSimpleName() + "[key=" + getKey() + ", name=" + getName() + ", appUrlPrefix="
+                               + appUrlPrefix + ", pltUrlPrefix=" + pltUrlPrefix + ", caasUrlPrefix=" + caasUrlPrefix + "]";
        }
 
        @Override
index 97f08ff..6a86ca1 100644 (file)
@@ -78,7 +78,7 @@ public class RicInstanceKeyName implements IDashboardResponse {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[key=" + getKey() + ", name=" + getName() + "]";
+               return this.getClass().getSimpleName() + "[key=" + getKey() + ", name=" + getName() + "]";
        }
 
        @Override
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegion.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegion.java
new file mode 100644 (file)
index 0000000..58e7e86
--- /dev/null
@@ -0,0 +1,111 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 AT&T Intellectual Property
+ * %%
+ * 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.oransc.ric.portal.dashboard.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Transport model for RIC region which has a list of instances.
+ */
+public class RicRegion implements IDashboardResponse {
+
+       private String name;
+       private List<RicInstance> instances;
+
+       /**
+        * Builds an empty object.
+        */
+       public RicRegion() {
+               // no-arg constructor
+       }
+
+       /**
+        * Convenience constructor for minimal value set.
+        * 
+        * @param name
+        *                 Name
+        */
+       public RicRegion(String name) {
+               this.name = name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String s) {
+               this.name = s;
+       }
+
+       public RicRegion name(String name) {
+               this.name = name;
+               return this;
+       }
+
+       public List<RicInstance> getInstances() {
+               return instances;
+       }
+
+       public void setInstances(List<RicInstance> instances) {
+               this.instances = instances;
+       }
+
+       /**
+        * Gets a list of key-name pairs.
+        * 
+        * @return List of RicInstanceKeyName objects.
+        */
+       public List<RicInstanceKeyName> getKeyNameList() {
+               List<RicInstanceKeyName> list = new ArrayList<>();
+               for (RicInstance i : instances)
+                       list.add(i.toKeyName());
+               return list;
+       }
+
+       @Override
+       public String toString() {
+               return this.getClass().getSimpleName() + "[name=" + getName() + ", instances=" + instances + "]";
+       }
+
+       @Override
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result + ((name == null) ? 0 : name.hashCode());
+               result = result * result + ((instances == null) ? 0 : instances.hashCode());
+               return result;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               RicRegion other = (RicRegion) obj;
+               return Objects.equals(name, other.name) && instances.size() == other.instances.size();
+       }
+
+}
@@ -2,7 +2,7 @@
  * ========================LICENSE_START=================================
  * O-RAN-SC
  * %%
- * Copyright (C) 2019 AT&T Intellectual Property
+ * Copyright (C) 2020 AT&T Intellectual Property
  * %%
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,36 +25,39 @@ import java.util.List;
 
 import org.oransc.ric.portal.dashboard.exception.UnknownInstanceException;
 
-public class RicInstanceList {
+/**
+ * Used as a bean to publish configuration data in a convenient way.
+ */
+public class RicRegionList {
 
-       private final List<RicInstance> instances;
+       private final List<RicRegion> regions;
 
-       public RicInstanceList() {
-               this.instances = new ArrayList<>();
+       public RicRegionList() {
+               this.regions = new ArrayList<>();
        }
 
-       public RicInstanceList(List<RicInstance> list) {
-               this.instances = list;
+       public RicRegionList(List<RicRegion> list) {
+               this.regions = list;
        }
 
-       public List<RicInstance> getInstances() {
-               return instances;
+       public List<RicRegion> getRegions() {
+               return regions;
        }
 
        /**
-        * Gets a list of key-name pairs.
+        * Builds a response that has only key-name pairs.
         * 
-        * @return List of RicInstanceKeyName objects.
+        * @return List of RicRegionTransport objects
         */
-       public List<RicInstanceKeyName> getKeyNameList() {
-               List<RicInstanceKeyName> list = new ArrayList<>();
-               for (RicInstance i : instances)
-                       list.add(i.toKeyName());
-               return list;
+       public List<RicRegionTransport> getSimpleInstances() {
+               List<RicRegionTransport> response = new ArrayList<>();
+               for (RicRegion r : regions)
+                       response.add(new RicRegionTransport().name(r.getName()).instances(r.getKeyNameList()));
+               return response;
        }
 
        /**
-        * Gets the instance with the specified key
+        * Gets the instance with the specified key in any region
         * 
         * @param instanceKey
         *                        Key to fetch
@@ -63,9 +66,16 @@ public class RicInstanceList {
         *                                      If the key is not known
         */
        public RicInstance getInstance(String instanceKey) {
-               for (RicInstance i : instances)
-                       if (i.getKey().equals(instanceKey))
-                               return i;
+               for (RicRegion r : regions)
+                       for (RicInstance i : r.getInstances())
+                               if (i.getKey().equals(instanceKey))
+                                       return i;
                throw new UnknownInstanceException(instanceKey);
        }
+
+       @Override
+       public String toString() {
+               return this.getClass().getSimpleName() + "[regions=" + regions + "]";
+       }
+
 }
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegionTransport.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/RicRegionTransport.java
new file mode 100644 (file)
index 0000000..1dfc821
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 AT&T Intellectual Property
+ * %%
+ * 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.oransc.ric.portal.dashboard.model;
+
+import java.util.List;
+
+/**
+ * Transport model for RIC region which has a list of instances with ONLY
+ * key-name pairs.
+ */
+public class RicRegionTransport implements IDashboardResponse {
+
+       private String name;
+       private List<RicInstanceKeyName> instances;
+
+       public String getName() {
+               return name;
+       }
+
+       public RicRegionTransport name(String name) {
+               this.name = name;
+               return this;
+       }
+
+       public List<RicInstanceKeyName> getInstances() {
+               return instances;
+       }
+
+       public RicRegionTransport instances(List<RicInstanceKeyName> instances) {
+               this.instances = instances;
+               return this;
+       }
+
+       @Override
+       public String toString() {
+               return this.getClass().getSimpleName() + "[name=" + getName() + ", instances=" + instances + "]";
+       }
+
+}
index c0c9a24..281d72f 100644 (file)
@@ -62,7 +62,7 @@ public class SuccessTransport implements IDashboardResponse {
 
        @Override
        public String toString() {
-               return this.getClass().getName() + "[status=" + getStatus() + ", data=" + getData() + "]";
+               return this.getClass().getSimpleName() + "[status=" + getStatus() + ", data=" + getData() + "]";
        }
 
 }
index 83b3b7c..da8fe99 100644 (file)
@@ -85,18 +85,32 @@ metrics:
         ac: http://jar-app-props-kibana-url-ac
         mc: http://jar-app-props-kibana-url-mc
 
-# Instances
+# Regions and instances
+# The regions are a just convenience for users,
+# the keys must be unique across all instances.
 ricinstances:
-    instances:
+    regions:
         -
-          key: i1
-          name: Primary RIC Instance
-          appUrlPrefix: App prefix 1
-          caasUrlPrefix: Caas prefix 1
-          pltUrlPrefix: Plt prefix 1
+          name: Region AAA
+          instances:
+              -
+                key: i1
+                name: Primary RIC Instance
+                appUrlPrefix: App prefix 1
+                caasUrlPrefix: Caas prefix 1
+                pltUrlPrefix: Plt prefix 1
+              -
+                key: i2
+                name: RIC Instance Nr Two
+                appUrlPrefix: App prefix 2
+                caasUrlPrefix: Caas prefix 2
+                pltUrlPrefix: Plt prefix 2
         -
-          key: i2
-          name: RIC Instance Nr Two
-          appUrlPrefix: App prefix 2
-          caasUrlPrefix: Caas prefix 2
-          pltUrlPrefix: Plt prefix 2
+          name: Region DDD
+          instances:
+              -
+                key: i3
+                name: RIC Instance in region D
+                appUrlPrefix: App prefix 3
+                caasUrlPrefix: Caas prefix 3
+                pltUrlPrefix: Plt prefix 3
index 15cab07..558ef09 100644 (file)
@@ -25,11 +25,10 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.oransc.ric.portal.dashboard.model.RicInstance;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegion;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
@@ -44,34 +43,37 @@ public class RICInstanceMockConfiguration {
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
        // Publish constants for use in tests
+       public static final String REGION_NAME_1 = "Region AAA";
+       public static final String REGION_NAME_2 = "Region DDD";
        public static final String INSTANCE_KEY_1 = "i1";
        public static final String INSTANCE_KEY_2 = "i2";
        public static final String[] INSTANCE_KEYS = { INSTANCE_KEY_1, INSTANCE_KEY_2 };
 
-       // Simulate remote method delay for UI testing
-       private int delayMs;
-
-       @Autowired
-       public RICInstanceMockConfiguration(@Value("${mock.config.delay:0}") int delayMs) {
-               logger.debug("ctor: configured with delay {}", delayMs);
-               this.delayMs = delayMs;
-       }
+       // No constructor needed, don't simulate delay from the Dashboard
 
        @Bean
-       public RicInstanceList ricInstanceList() throws InterruptedException {
-               if (delayMs > 0) {
-                       logger.debug("ricInstanceList sleeping {}", delayMs);
-                       Thread.sleep(delayMs);
-               }
-               List<RicInstance> instances = new ArrayList<>();
-               for (String key : INSTANCE_KEYS) {
-                       RicInstance i = new RicInstance().key(key).name("RIC Instance " + key)
-                                       .appUrlPrefix("http://" + key + ".domain.name/app")
-                                       .pltUrlPrefix("http://" + key + ".domain.name/plt")
-                                       .caasUrlPrefix("http://" + key + ".domain.name/caas");
-                       instances.add(i);
-               }
-               return new RicInstanceList(instances);
+       public RicRegionList ricRegions() {
+               logger.debug("Creating mock bean ricRegions");
+               List<RicRegion> regions = new ArrayList<>();
+               RicRegion region1 = new RicRegion().name(REGION_NAME_1);
+               regions.add(region1);
+               List<RicInstance> instances1 = new ArrayList<>();
+               region1.setInstances(instances1);
+               String key1 = INSTANCE_KEY_1;
+               instances1.add(new RicInstance().key(key1).name("RIC Instance " + key1) //
+                               .appUrlPrefix("http://" + key1 + ".domain.name/app") //
+                               .pltUrlPrefix("http://" + key1 + ".domain.name/plt") //
+                               .caasUrlPrefix("http://" + key1 + ".domain.name/caas"));
+               RicRegion region2 = new RicRegion().name(REGION_NAME_2);
+               regions.add(region2);
+               List<RicInstance> instances2 = new ArrayList<>();
+               region2.setInstances(instances2);
+               String key2 = INSTANCE_KEY_2;
+               instances2.add(new RicInstance().key(key2).name("RIC Instance " + key2) //
+                               .appUrlPrefix("http://" + key2 + ".domain.name/app") //
+                               .pltUrlPrefix("http://" + key2 + ".domain.name/plt") //
+                               .caasUrlPrefix("http://" + key2 + ".domain.name/caas"));
+               return new RicRegionList(regions);
        }
 
 }
index ebe7804..71a575f 100644 (file)
@@ -24,7 +24,7 @@ import java.lang.invoke.MethodHandles;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.oransc.ric.portal.dashboard.model.RicInstanceList;
+import org.oransc.ric.portal.dashboard.model.RicRegionList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,7 +42,7 @@ public class AbstractConfigTest {
 
        // Relies on Spring-Boot feature of populating instances data from configuration
        @Autowired
-       protected RicInstanceList instanceConfig;
+       protected RicRegionList instanceConfig;
 
        // Sonar finds the annotations on this class and insists on at least one test.
        @Test
index 403c33b..43a77f1 100644 (file)
@@ -40,6 +40,11 @@ public class DefaultContextTest {
 
        private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+       // If this test is annotated and run by maven, two cases in
+       // PortalRestCentralServiceTest fail. I traced it down to a second
+       // occurrence of the Dashboard user manager bean, which I can only speculate
+       // gets instantiated when the active profile is not "test". Because I cannot
+       // explain nor fix the behavior, this remains commented out.
        // @Test
        public void contextLoads() {
                // Silence Sonar warning about missing assertion.
index 8d9dd98..c668afc 100644 (file)
@@ -175,13 +175,29 @@ public class ModelTest extends AbstractModelTest {
                logger.info(m.toString());
        }
 
+       private void checkRicRegion(RicRegion m) {
+               Assert.assertEquals(s1, m.getName());
+       }
+
+       @Test
+       public void testRicRegion() {
+               RicRegion m = new RicRegion();
+               m.setName(s1);
+               checkRicRegion(m);
+               Assert.assertTrue(m.equals(m));
+               Assert.assertFalse(m.equals(null));
+               Assert.assertFalse(m.equals(new RicRegion()));
+               Assert.assertNotEquals(1, m.hashCode());
+               logger.info(m.toString());
+       }
+
        @Test
-       public void testRicInstanceList() {
-               RicInstanceList m = new RicInstanceList();
-               List<RicInstance> list = new ArrayList<>();
-               m = new RicInstanceList(list);
-               Assert.assertEquals(list, m.getInstances());
-               Assert.assertNotNull(m.getKeyNameList());
+       public void testRicRegionList() {
+               RicRegionList m = new RicRegionList();
+               List<RicRegion> list = new ArrayList<>();
+               m = new RicRegionList(list);
+               Assert.assertEquals(list, m.getRegions());
+               Assert.assertNotNull(m.getSimpleInstances());
                try {
                        m.getInstance(s1);
                } catch (UnknownInstanceException ex) {
index 1c57005..ea0dff1 100644 (file)
@@ -59,4 +59,9 @@ export interface EcompUser {
 export interface RicInstance {
   key: string;
   name: string;
-}
\ No newline at end of file
+}
+
+export interface RicRegion {
+  name: string;
+  instances: Array<RicInstance>;
+}
index fbc6bbb..c1afb18 100644 (file)
@@ -22,7 +22,7 @@ import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { shareReplay, tap } from 'rxjs/operators';
-import { RicInstance } from '../../interfaces/dashboard.types';
+import { RicInstance, RicRegion } from '../../interfaces/dashboard.types';
 import { DashboardService } from '../dashboard/dashboard.service';
 
 @Injectable({
@@ -30,20 +30,20 @@ import { DashboardService } from '../dashboard/dashboard.service';
 })
 export class InstanceSelectorService {
 
-  private instanceArray: Observable<RicInstance[]>;
-  private selectedInstance: BehaviorSubject<RicInstance> = new BehaviorSubject<RicInstance>({ key: '', name:''});
+  private allInstances: Observable<RicRegion[]>;
+  private selectedInstance: BehaviorSubject<RicInstance> = new BehaviorSubject<RicInstance>({ key: '', name: '' });
 
   constructor(
     private dashboardSvc: DashboardService,
     private httpClient: HttpClient) {
   }
 
-  getInstanceArray(): Observable<RicInstance[]> {
-    if (this.instanceArray) {
-      return this.instanceArray;
+  getAllInstances(): Observable<RicRegion[]> {
+    if (this.allInstances) {
+      return this.allInstances;
     }
     const path = this.dashboardSvc.buildPath('admin', null, 'instance');
-    return this.instanceArray = this.httpClient.get<RicInstance[]>(path)
+    return this.allInstances = this.httpClient.get<RicRegion[]>(path)
       .pipe(
         shareReplay(1)
       );
@@ -57,7 +57,7 @@ export class InstanceSelectorService {
   }
 
   updateSelectedInstance(instance: RicInstance) {
-    this.selectedInstance.next(instance)
+    this.selectedInstance.next(instance);
   }
 
 }
index 2b9d22a..6df47db 100644 (file)
   Select RIC Instance
 </div>
 <form [formGroup]="instanceForm" novalidate autocomplete="off" (ngSubmit)="changeInstance(instanceForm.value.instance)">
+  <div mat-dialog-content>
+    <mat-form-field>
+      <mat-label>Select a region</mat-label>
+      <mat-select (selectionChange)="changeRegion($event.value)">
+        <mat-option *ngFor="let region of allRegions" [value]="region">
+          {{region.name}}
+        </mat-option>
+      </mat-select>
+    </mat-form-field>
+  </div>
   <div mat-dialog-content>
     <mat-form-field>
       <mat-label>Select an instance</mat-label>
       <mat-select formControlName="instance">
-        <mat-option *ngFor="let instance of instanceArray" [value]="instance">
+        <mat-option *ngFor="let instance of regionInstances" [value]="instance">
           {{instance.name}}
         </mat-option>
       </mat-select>
index 8c6159c..c9f927c 100644 (file)
  * ========================LICENSE_END===================================
  */
 
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { FormControl, FormGroup, Validators } from '@angular/forms';
 import { MatDialogRef } from '@angular/material/dialog';
-import { Subscription } from 'rxjs';
 import { finalize } from 'rxjs/operators';
-import { RicInstance } from '../../interfaces/dashboard.types';
+import { RicInstance, RicRegion } from '../../interfaces/dashboard.types';
 import { InstanceSelectorService } from '../../services/instance-selector/instance-selector.service';
 import { LoadingDialogService } from '../../services/ui/loading-dialog.service';
 
@@ -31,11 +30,11 @@ import { LoadingDialogService } from '../../services/ui/loading-dialog.service';
   selector: 'rd-instance-selector-dialog',
   templateUrl: './instance-selector-dialog.component.html',
 })
-export class InstanceSelectorDialogComponent implements OnInit, OnDestroy  {
+export class InstanceSelectorDialogComponent implements OnInit  {
 
-  private instanceArray: RicInstance[];
+  private allRegions: RicRegion[];
+  private regionInstances: RicInstance[];
   private instanceForm: FormGroup;
-  private instanceChange: Subscription;
 
   constructor(
     private dialogRef: MatDialogRef<InstanceSelectorDialogComponent>,
@@ -44,32 +43,26 @@ export class InstanceSelectorDialogComponent implements OnInit, OnDestroy  {
 
   ngOnInit() {
     this.instanceForm = new FormGroup({
-      instance: new FormControl('', [Validators.required]),
-    })
+      instance: new FormControl('', [Validators.required])
+    });
 
     this.loadingDialogService.startLoading('Loading RIC instances');
-    this.instanceSelectorService.getInstanceArray()
+    this.instanceSelectorService.getAllInstances()
       .pipe(
         finalize(() => this.loadingDialogService.stopLoading())
       )
-      .subscribe((instanceArray: RicInstance[]) => {
-        this.instanceArray = instanceArray;
-      })
-
-    this.instanceChange = this.instanceSelectorService.getSelectedInstance().subscribe((selectedInstance: RicInstance) => {
-      if (selectedInstance.key) {
-        this.instanceForm.setValue({ instance: selectedInstance })
-      }
-    });
+      .subscribe((regArray: RicRegion[]) => {
+        this.allRegions = regArray;
+      });
   }
 
-  ngOnDestroy() {
-    this.instanceChange.unsubscribe();
-  }
-
-  changeInstance(selectedInstance) {
+  changeInstance(selectedInstance: RicInstance) {
     this.instanceSelectorService.updateSelectedInstance(selectedInstance);
     this.dialogRef.close(true);
   }
 
+  changeRegion(selectedRegion: RicRegion) {
+    this.instanceForm.setValue({ instance: '' });
+    this.regionInstances = selectedRegion.instances;
+  }
 }