Non-RT RIC Dashboard
First commit
Change-Id: I9e140d31d65d13df3ce07f6b87eac250ee952eab
Issue-ID: NONRTRIC-61
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
diff --git a/dashboard/webapp-backend/.gitignore b/dashboard/webapp-backend/.gitignore
new file mode 100644
index 0000000..02dbd44
--- /dev/null
+++ b/dashboard/webapp-backend/.gitignore
@@ -0,0 +1,34 @@
+/.classpath
+/.project
+/.settings
+/target/
+/logs/
+/.mvn/wrapper/maven-wrapper.jar
+/logs/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+/application-tlab2.properties
+/application.properties
+/dashboard-users.json
diff --git a/dashboard/webapp-backend/README.md b/dashboard/webapp-backend/README.md
new file mode 100644
index 0000000..5dd1b1e
--- /dev/null
+++ b/dashboard/webapp-backend/README.md
@@ -0,0 +1,25 @@
+# RIC Dashboard Web Application Backend
+
+The RIC Dashboard back-end provides REST services to the Dashboard
+front-end Typescript features running in the user's browser. For
+production use, it also serves the Angular application files.
+
+Please see the documentation in the docs/ folder.
+
+The backend server publishes live API documentation at the
+URL `http://your-host-name-here:8080/swagger-ui.html`
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. 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.
diff --git a/dashboard/webapp-backend/config/.gitignore b/dashboard/webapp-backend/config/.gitignore
new file mode 100644
index 0000000..edd66f1
--- /dev/null
+++ b/dashboard/webapp-backend/config/.gitignore
@@ -0,0 +1,2 @@
+/key.properties
+/portal.properties
diff --git a/dashboard/webapp-backend/config/key.properties.template b/dashboard/webapp-backend/config/key.properties.template
new file mode 100644
index 0000000..a8d44e3
--- /dev/null
+++ b/dashboard/webapp-backend/config/key.properties.template
@@ -0,0 +1,21 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# Template for the file that provides a secret key for the RIC Dashboard.
+
+cipher.enc.key =
diff --git a/dashboard/webapp-backend/config/portal.properties.template b/dashboard/webapp-backend/config/portal.properties.template
new file mode 100644
index 0000000..8c7fec7
--- /dev/null
+++ b/dashboard/webapp-backend/config/portal.properties.template
@@ -0,0 +1,34 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# Template for the file that provides properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+# The following properties are the same in every deployment
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+
+# The following properties are DIFFERENT in every deployment
+
+# URL of portal login screen
+ecomp_redirect_url = http://localhost/portal
+# URL of portal API
+ecomp_rest_url = http://localhost/portal
+# Value assigned by portal instance
+ueb_app_key = abcdef1234567890
diff --git a/dashboard/webapp-backend/pom.xml b/dashboard/webapp-backend/pom.xml
new file mode 100644
index 0000000..ec4fdea
--- /dev/null
+++ b/dashboard/webapp-backend/pom.xml
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--<![CDATA[
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2019 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===================================
+]]>-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.o-ran-sc.portal.ric-dashboard</groupId>
+ <artifactId>ric-dash-parent</artifactId>
+ <version>1.2.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>ric-dash-be</artifactId>
+ <name>RIC Dashboard Webapp backend</name>
+ <properties>
+ <springfox.version>2.9.2</springfox.version>
+ <!-- Set by Jenkins -->
+ <build.number>0</build.number>
+ </properties>
+ <repositories>
+ <repository>
+ <id>onap-releases</id>
+ <name>ONAP - Release Repository</name>
+ <url>https://nexus.onap.org/content/repositories/releases</url>
+ </repository>
+ </repositories>
+ <dependencies>
+ <!-- Platform components -->
+ <dependency>
+ <groupId>org.o-ran-sc.ric.plt.a1controller.client</groupId>
+ <artifactId>a1-controller-client</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.portal.sdk</groupId>
+ <artifactId>epsdk-fw</artifactId>
+ <version>2.6.0</version>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>apache-log4j-extras</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </exclusion>
+ <!-- EELF omits "test" scope on this dependency -->
+ <exclusion>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ </exclusion>
+ <!-- EELF omits "test" scope on this dependency -->
+ <exclusion>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <!-- Bridge uses of Apache commons logging, like EPSDK-FW -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <!-- Test dependencies -->
+ <!-- Mockito supports development, not just testing -->
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.platform</groupId>
+ <artifactId>junit-platform-launcher</artifactId>
+ <!-- Override Spring-Boot choice for Eclipse -->
+ <version>1.4.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <!-- Most configuration and all execution is inherited -->
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <configuration>
+ <roots>
+ <root>src</root>
+ </roots>
+ <excludes>
+ <exclude>**/*.json</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <!-- Add the build number to the jar manifest. Spring-Boot uses a complex
+ packaging process that makes access to the original Manifest.MF very difficult.
+ However, Java provides access to the implementation version for a package,
+ so cram the build number into there. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+ </manifest>
+ <manifestEntries>
+ <Implementation-Version>${project.version}-b${build.number}</Implementation-Version>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/classes/resources/</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.parent.basedir}/webapp-frontend/dist/dashApp/</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- do not deploy a jar or pom file -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.4</version>
+ <executions>
+ <execution>
+ <id>default-prepare-agent</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-report</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- https://stackoverflow.com/questions/39126226/fabric8-springboot-full-example -->
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>0.30.0</version>
+ <configuration>
+ <verbose>true</verbose>
+ <!-- environment variables supplied by Jenkins -->
+ <pullRegistry>${env.CONTAINER_PULL_REGISTRY}</pullRegistry>
+ <pushRegistry>${env.CONTAINER_PUSH_REGISTRY}</pushRegistry>
+ <images>
+ <image>
+ <!-- Specify a tag to avoid default tag "latest" -->
+ <!-- Avoid maven artifact name here -->
+ <name>ric-dashboard:${project.version}</name>
+ <build>
+ <from>openjdk:11-jre-slim</from>
+ <tags>
+ <!-- Add tag with build number -->
+ <tag>${project.version}</tag>
+ </tags>
+ <assembly>
+ <descriptorRef>artifact</descriptorRef>
+ </assembly>
+ <runCmds>
+ <!-- Ensure logs dir exists and is world writable -->
+ <runCmd>mkdir /logs</runCmd>
+ <runCmd>chmod -R 777 /logs</runCmd>
+ </runCmds>
+ <cmd>
+ <!-- Include maven dir on classpath for prop files -->
+ <exec>
+ <arg>java</arg>
+ <arg>-Xms128m</arg>
+ <arg>-Xmx256m</arg>
+ <arg>-cp</arg>
+ <arg>maven:maven/${project.artifactId}-${project.version}.${project.packaging}</arg>
+ <arg>-Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication</arg>
+ <arg>-Djava.security.egd=file:/dev/./urandom</arg>
+ <arg>org.springframework.boot.loader.PropertiesLauncher</arg>
+ </exec>
+ </cmd>
+ </build>
+ </image>
+ </images>
+ </configuration>
+ <!-- build Docker images in install phase, push in deploy phase -->
+ <executions>
+ <execution>
+ <goals>
+ <goal>build</goal>
+ <goal>push</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
new file mode 100644
index 0000000..333c532
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
@@ -0,0 +1,60 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+// Limit scan to dashboard classes; exclude generated API classes
+@ComponentScan("org.oransc.ric.portal.dashboard")
+public class DashboardApplication {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static void main(String[] args) throws IOException {
+ SpringApplication.run(DashboardApplication.class, args);
+ // Ensure this appears on the console by using level WARN
+ logger.warn("main: version '{}' successful start",
+ getImplementationVersion(MethodHandles.lookup().lookupClass()));
+ }
+
+ /**
+ * Gets version details for the specified class.
+ *
+ * @param clazz
+ * Class to get the version
+ *
+ * @return the value of the MANIFEST.MF property Implementation-Version as
+ * written by maven when packaged in a jar; 'unknown' otherwise.
+ */
+ public static String getImplementationVersion(Class<?> clazz) {
+ String classPath = clazz.getResource(clazz.getSimpleName() + ".class").toString();
+ return classPath.startsWith("jar") ? clazz.getPackage().getImplementationVersion() : "unknown-not-jar";
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
new file mode 100644
index 0000000..d61ce1d
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+public abstract class DashboardConstants {
+
+ private DashboardConstants() {
+ // Sonar insists on hiding the constructor
+ }
+
+ public static final String ENDPOINT_PREFIX = "/api";
+ // Factor out method names used in multiple controllers
+ public static final String VERSION_METHOD = "version";
+ public static final String APP_NAME_AC = "AC";
+ public static final String APP_NAME_MC = "MC";
+ // The role names are defined by ONAP Portal.
+ // The prefix "ROLE_" is required by Spring.
+ // These are used in Java code annotations that require constants.
+ public static final String ROLE_NAME_STANDARD = "Standard_User";
+ public static final String ROLE_NAME_ADMIN = "System_Administrator";
+ private static final String ROLE_PREFIX = "ROLE_";
+ public static final String ROLE_ADMIN = ROLE_PREFIX + ROLE_NAME_ADMIN;
+ public static final String ROLE_STANDARD = ROLE_PREFIX + ROLE_NAME_STANDARD;
+ public static final String A1_CONTROLLER_USERNAME = "admin";
+ public static final String A1_CONTROLLER_PASSWORD = "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U";
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java
new file mode 100644
index 0000000..c5fd101
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java
@@ -0,0 +1,184 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Provides simple user-management services.
+ *
+ * This first implementation serializes user details to a file.
+ *
+ * TODO: migrate to a database.
+ */
+public class DashboardUserManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // This default value is only useful for development and testing.
+ public static final String USER_FILE_PATH = "dashboard-users.json";
+
+ private final File userFile;
+ private final List<EcompUser> users;
+
+ /**
+ * Development/test-only constructor that uses default file path.
+ *
+ * @param clear
+ * If true, start empty and remove any existing file.
+ *
+ * @throws IOException
+ * On file error
+ */
+ public DashboardUserManager(boolean clear) throws IOException {
+ this(USER_FILE_PATH);
+ if (clear) {
+ logger.debug("ctor: removing file {}", userFile.getAbsolutePath());
+ File f = new File(DashboardUserManager.USER_FILE_PATH);
+ if (f.exists())
+ f.delete();
+ users.clear();
+ }
+ }
+
+ /**
+ * Constructur that accepts a file path
+ *
+ * @param userFilePath
+ * File path
+ * @throws IOException
+ * If file cannot be read
+ */
+ public DashboardUserManager(final String userFilePath) throws IOException {
+ logger.debug("ctor: userfile {}", userFilePath);
+ if (userFilePath == null)
+ throw new IllegalArgumentException("Missing or empty user file property");
+ userFile = new File(userFilePath);
+ logger.debug("ctor: managing users in file {}", userFile.getAbsolutePath());
+ if (userFile.exists()) {
+ final ObjectMapper mapper = new ObjectMapper();
+ users = mapper.readValue(userFile, new TypeReference<List<EcompUser>>() {
+ });
+ } else {
+ users = new ArrayList<>();
+ }
+ }
+
+ /**
+ * Gets the current users.
+ *
+ * @return List of EcompUser objects, possibly empty
+ */
+ public List<EcompUser> getUsers() {
+ return this.users;
+ }
+
+ /**
+ * Gets the user with the specified login Id
+ *
+ * @param loginId
+ * Desired login Id
+ * @return User object; null if Id is not known
+ */
+ public EcompUser getUser(String loginId) {
+ for (EcompUser u : this.users) {
+ if (u.getLoginId().equals(loginId)) {
+ logger.debug("getUser: match on {}", loginId);
+ return u;
+ }
+ }
+ logger.debug("getUser: no match on {}", loginId);
+ return null;
+ }
+
+ private void saveUsers() throws JsonGenerationException, JsonMappingException, IOException {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(userFile, users);
+ }
+
+ /*
+ * Allow at most one thread to create a user at one time.
+ */
+ public synchronized void createUser(EcompUser user) throws PortalAPIException {
+ logger.debug("createUser: loginId is " + user.getLoginId());
+ if (users.contains(user))
+ throw new PortalAPIException("User exists: " + user.getLoginId());
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+ /*
+ * Allow at most one thread to modify a user at one time. We still have
+ * last-edit-wins of course.
+ */
+ public synchronized void updateUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: loginId is " + loginId);
+ int index = users.indexOf(user);
+ if (index < 0)
+ throw new PortalAPIException("User does not exist: " + user.getLoginId());
+ users.remove(index);
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+ // Test infrastructure
+ public static void main(String[] args) throws Exception {
+ DashboardUserManager dum = new DashboardUserManager(false);
+ EcompUser user = new EcompUser();
+ user.setActive(true);
+ user.setLoginId("demo");
+ user.setFirstName("First");
+ user.setLastName("Last");
+ EcompRole role = new EcompRole();
+ role.setId(1L);
+ role.setName(DashboardConstants.ROLE_NAME_ADMIN);
+ Set<EcompRole> roles = new HashSet<>();
+ roles.add(role);
+ user.setRoles(roles);
+ dum.createUser(user);
+ logger.debug("Created user {}", user);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java
new file mode 100644
index 0000000..ffdcacd
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java
@@ -0,0 +1,74 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.config;
+
+import java.lang.invoke.MethodHandles;
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.invoker.ApiClient;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+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;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+
+/**
+ * Creates an A1 controller client as a bean to be managed by the Spring
+ * container.
+ */
+@Configuration
+@Profile("!test")
+public class A1ControllerConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ public static final String A1_CONTROLLER_USERNAME = DashboardConstants.A1_CONTROLLER_USERNAME;
+ public static final String A1_CONTROLLER_PASSWORD = DashboardConstants.A1_CONTROLLER_PASSWORD;
+
+ // Populated by the autowired constructor
+ private final String a1ControllerUrl;
+
+ @Autowired
+ public A1ControllerConfiguration(@Value("${a1controller.url.prefix}") final String urlPrefix, //
+ @Value("${a1controller.url.suffix}") final String urlSuffix) {
+ logger.debug("ctor prefix '{}' suffix '{}'", urlPrefix, urlSuffix);
+ a1ControllerUrl = new DefaultUriBuilderFactory(urlPrefix.trim()).builder().path(urlSuffix.trim()).build().normalize()
+ .toString();
+ logger.info("Configuring A1 Controller at URL {}", a1ControllerUrl);
+ }
+
+ private ApiClient apiClient() {
+ ApiClient apiClient = new ApiClient(new RestTemplate());
+ apiClient.setBasePath(a1ControllerUrl);
+ apiClient.setUsername(A1_CONTROLLER_USERNAME);
+ apiClient.setPassword(A1_CONTROLLER_PASSWORD);
+ return apiClient;
+ }
+
+ @Bean
+ // The bean (method) name must be globally unique
+ public A1ControllerApi a1ControllerApi() {
+ return new A1ControllerApi(apiClient());
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java
new file mode 100644
index 0000000..696d74f
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+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;
+
+/**
+ * Creates an instance of the user manager.
+ */
+@Configuration
+@Profile("!test")
+public class AdminConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Populated by the autowired constructor
+ private final String userfile;
+
+ @Autowired
+ public AdminConfiguration(@Value("${userfile}") final String userfile) {
+ logger.debug("ctor userfile '{}'", userfile);
+ this.userfile = userfile;
+ }
+
+ @Bean
+ // The bean (method) name must be globally unique
+ public DashboardUserManager userManager() throws IOException {
+ return new DashboardUserManager(userfile);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java
new file mode 100644
index 0000000..f318cad
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import java.lang.invoke.MethodHandles;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@Profile("!test")
+public class PortalApiConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Instantiates the EPSDK-FW servlet that implements the API called by Portal.
+ * Needed because this app is not configured to scan the EPSDK-FW packages;
+ * there's also a chance that Spring-Boot does not automatically
+ * process @WebServlet annotations.
+ *
+ * @return Servlet registration bean for the Portal Rest API proxy servlet.
+ */
+ @Bean
+ public ServletRegistrationBean<PortalRestAPIProxy> portalApiProxyServletBean() {
+ logger.debug("portalApiProxyServletBean");
+ PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+ final ServletRegistrationBean<PortalRestAPIProxy> servletBean = new ServletRegistrationBean<>(servlet,
+ PortalApiConstants.API_PREFIX + "/*");
+ servletBean.setName("PortalRestApiProxyServlet");
+ return servletBean;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
new file mode 100644
index 0000000..3a87782
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Allows non-Spring-managed classes to obtain the Spring application context.
+ */
+@Component
+public class SpringContextCache implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext = null;
+
+ @Override
+ public void setApplicationContext(final ApplicationContext appContext) throws BeansException {
+ applicationContext = appContext;
+ }
+
+ public static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java
new file mode 100644
index 0000000..b4bb0a3
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java
@@ -0,0 +1,68 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import org.oransc.ric.portal.dashboard.DashboardApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * http://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfiguration {
+
+ /**
+ * @return new Docket
+ */
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2).select() //
+ .apis(RequestHandlerSelectors.basePackage(DashboardApplication.class.getPackage().getName())) //
+ .paths(PathSelectors.any()) //
+ .build() //
+ .apiInfo(apiInfo());
+ }
+
+ private ApiInfo apiInfo() {
+ final String version = DashboardApplication.class.getPackage().getImplementationVersion();
+ return new ApiInfoBuilder() //
+ .title("RIC Dashboard backend") //
+ .description("Proxies access to RIC services.")//
+ .termsOfServiceUrl("Terms of service") //
+ .contact(new Contact("RIC Dashboard Dev Team", //
+ "http://no-docs-yet.org/", //
+ "noreply@O-RAN-SC.org")) //
+ .license("Apache 2.0 License").licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") //
+ .version(version == null ? "version not available" : version) //
+ .build();
+ }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
new file mode 100644
index 0000000..b912500
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
@@ -0,0 +1,125 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.controller.A1Controller;
+import org.oransc.ric.portal.dashboard.controller.SimpleErrorController;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthenticationFilter;
+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;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("!test")
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Although constructor arguments are recommended over field injection,
+ // this results in fewer lines of code.
+ @Value("${portalapi.security}")
+ private Boolean portalapiSecurity;
+ @Value("${portalapi.appname}")
+ private String appName;
+ @Value("${portalapi.username}")
+ private String userName;
+ @Value("${portalapi.password}")
+ private String password;
+ @Value("${portalapi.decryptor}")
+ private String decryptor;
+ @Value("${portalapi.usercookie}")
+ private String userCookie;
+
+ @Autowired
+ DashboardUserManager userManager;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ logger.debug("configure: portalapi.username {}", userName);
+ // A chain of ".and()" always baffles me
+ http.authorizeRequests().anyRequest().authenticated();
+ http.headers().frameOptions().disable();
+ http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+ http.addFilterBefore(portalAuthenticationFilterBean(), BasicAuthenticationFilter.class);
+ }
+
+ /**
+ * Resource paths that do not require authentication, especially including
+ * Swagger-generated documentation.
+ */
+ public static final String[] OPEN_PATHS = { //
+ "/v2/api-docs", //
+ "/swagger-resources/**", //
+ "/swagger-ui.html", //
+ "/webjars/**", //
+ PortalApiConstants.API_PREFIX + "/**", //
+ A1Controller.CONTROLLER_PATH + "/" + A1Controller.VERSION_METHOD, //
+ SimpleErrorController.ERROR_PATH };
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // This disables Spring security, but not the app's filter.
+ web.ignoring().antMatchers(OPEN_PATHS);
+ }
+
+ @Bean
+ public PortalAuthManager portalAuthManagerBean()
+ throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+ return new PortalAuthManager(appName, userName, password, decryptor, userCookie);
+ }
+
+ /*
+ * If this is annotated with @Bean, it is created automatically AND REGISTERED,
+ * and Spring processes annotations in the source of the class. However, the
+ * filter is added in the chain apparently in the wrong order. Alternately, with
+ * no @Bean and added to the chain up in the configure() method in the desired
+ * order, the ignoring() matcher pattern configured above causes Spring to
+ * bypass this filter, which seems to me means the filter participates
+ * correctly.
+ */
+ public PortalAuthenticationFilter portalAuthenticationFilterBean()
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException,
+ IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+ PortalAuthenticationFilter portalAuthenticationFilter = new PortalAuthenticationFilter(portalapiSecurity,
+ portalAuthManagerBean(), this.userManager);
+ return portalAuthenticationFilter;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java
new file mode 100644
index 0000000..386d43c
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java
@@ -0,0 +1,220 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchemaInput;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPISchema;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchema;
+import org.oransc.ric.portal.dashboard.DashboardApplication;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.model.PolicyInstance;
+import org.oransc.ric.portal.dashboard.model.PolicyInstances;
+import org.oransc.ric.portal.dashboard.model.PolicyType;
+import org.oransc.ric.portal.dashboard.model.PolicyTypes;
+import org.oransc.ric.portal.dashboard.model.SuccessTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * Proxies calls from the front end to the A1 Controller via the A1 Mediator
+ * API.
+ *
+ * If a method throws RestClientResponseException, it is handled by
+ * {@link CustomResponseEntityExceptionHandler#handleProxyMethodException(Exception,
+ * org.springframework.web.context.request.WebRequest)}
+ * which returns status 502. All other exceptions are handled by Spring which
+ * returns status 500.
+ */
+@RestController
+@RequestMapping(value = A1Controller.CONTROLLER_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class A1Controller {
+
+ private static final String NEAR_RT_RIC_ID = "NearRtRic1";
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Publish paths in constants so tests are easy to write
+ public static final String CONTROLLER_PATH = DashboardConstants.ENDPOINT_PREFIX + "/policy";
+ // Endpoints
+ public static final String VERSION_METHOD = DashboardConstants.VERSION_METHOD;
+ public static final String POLICY_TYPES_METHOD = "policytypes";
+ public static final String POLICY_TYPE_ID_NAME = "policy_type_id";
+ public static final String POLICIES_NAME = "policies";
+ public static final String POLICY_INSTANCE_ID_NAME = "policy_instance_id";
+
+ // Populated by the autowired constructor
+ private final A1ControllerApi a1ControllerApi;
+
+ @Autowired
+ public A1Controller(final A1ControllerApi A1ControllerApi) {
+ Assert.notNull(A1ControllerApi, "API must not be null");
+ this.a1ControllerApi = A1ControllerApi;
+ if (logger.isDebugEnabled())
+ logger.debug("ctor: configured with client type {}", A1ControllerApi.getClass().getName());
+ }
+
+ @ApiOperation(value = "Gets the A1 client library MANIFEST.MF property Implementation-Version.",
+ response = SuccessTransport.class)
+ @GetMapping(VERSION_METHOD)
+ // No role required
+ public SuccessTransport getA1ControllerClientVersion() {
+ return new SuccessTransport(200, DashboardApplication.getImplementationVersion(A1ControllerApi.class));
+ }
+
+ /*
+ * The fields are defined in the A1Control Typescript interface.
+ */
+ @ApiOperation(value = "Gets the policy types from Near Realtime-RIC via the A1 Controller API")
+ @GetMapping(POLICY_TYPES_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+ public Object getAllPolicyTypes(HttpServletResponse response) {
+ logger.debug("getAllPolicyTypes");
+ InputNRRidSchemaInput nrrid = new InputNRRidSchemaInput();
+ nrrid.setNearRtRicId(NEAR_RT_RIC_ID);
+ InputNRRidSchema inputSchema = new InputNRRidSchema();
+ inputSchema.setInput(nrrid);
+ OutputPTidsListSchema outputPTidsListSchema =
+ a1ControllerApi.a1ControllerGetAllPolicyTypes(inputSchema);
+ List<Integer> policyTypeIds = outputPTidsListSchema.getOutput().getPolicyTypeIdList();
+ PolicyTypes policyTypes = new PolicyTypes();
+ InputNRRidPTidSchema typeSchema = new InputNRRidPTidSchema();
+ InputNRRidPTidSchemaInput typeId = new InputNRRidPTidSchemaInput();
+ typeId.setNearRtRicId(NEAR_RT_RIC_ID);
+ for (Integer policyTypeId : policyTypeIds) {
+ typeId.setPolicyTypeId(policyTypeId);
+ typeSchema.setInput(typeId);
+ OutputDescNamePTSchema controllerGetPolicyType =
+ a1ControllerApi.a1ControllerGetPolicyType(typeSchema);
+ OutputDescNamePTSchemaOutput policyTypeSchema = controllerGetPolicyType.getOutput();
+ PolicyType type = new PolicyType(policyTypeId, policyTypeSchema.getName(),
+ policyTypeSchema.getDescription(), policyTypeSchema.getPolicyType().toString());
+ policyTypes.add(type);
+ }
+ return policyTypes;
+ }
+
+ @ApiOperation(value = "Returns the policy instances for the given policy type.")
+ @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+ public Object getPolicyInstances(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString) {
+ logger.debug("getPolicyInstances {}", policyTypeIdString);
+ InputNRRidPTidSchemaInput typeIdInput = new InputNRRidPTidSchemaInput();
+ typeIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+ Integer policyTypeId = Integer.decode(policyTypeIdString);
+ typeIdInput.setPolicyTypeId(policyTypeId);
+ InputNRRidPTidSchema inputSchema = new InputNRRidPTidSchema();
+ inputSchema.setInput(typeIdInput);
+ OutputPIidsListSchema controllerGetAllInstancesForType =
+ a1ControllerApi.a1ControllerGetAllInstancesForType(inputSchema);
+ List<String> instancesForType = controllerGetAllInstancesForType.getOutput().getPolicyInstanceIdList();
+ PolicyInstances instances = new PolicyInstances();
+ InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+ instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+ instanceIdInput.setPolicyTypeId(policyTypeId);
+ InputNRRidPTidPIidSchema instanceInputSchema = new InputNRRidPTidPIidSchema();
+ for (String instanceId : instancesForType) {
+ instanceIdInput.setPolicyInstanceId(instanceId);
+ instanceInputSchema.setInput(instanceIdInput);
+ OutputPISchema policyInstance =
+ a1ControllerApi.a1ControllerGetPolicyInstance(instanceInputSchema);
+ PolicyInstance instance =
+ new PolicyInstance(instanceId, policyInstance.getOutput().getPolicyInstance());
+ instances.add(instance);
+ }
+ return instances;
+ }
+
+ @ApiOperation(value = "Returns a policy instance of a type")
+ @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+ + POLICY_INSTANCE_ID_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+ public Object getPolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+ @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId) {
+ logger.debug("getPolicyInstance {}:{}", policyTypeIdString, policyInstanceId);
+ InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+ instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+ instanceIdInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+ instanceIdInput.setPolicyInstanceId(policyInstanceId);
+ InputNRRidPTidPIidSchema inputSchema = new InputNRRidPTidPIidSchema();
+ inputSchema.setInput(instanceIdInput);
+ OutputPISchema policyInstance = a1ControllerApi.a1ControllerGetPolicyInstance(inputSchema);
+ return policyInstance.getOutput().getPolicyInstance();
+ }
+
+ @ApiOperation(value = "Creates the policy instances for the given policy type.")
+ @PutMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+ + POLICY_INSTANCE_ID_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
+ public void putPolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+ @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId, @RequestBody String instance) {
+ logger.debug("putPolicyInstance typeId: {}, instanceId: {}, instance: {}", policyTypeIdString,
+ policyInstanceId, instance);
+ InputNRRidPTidPIidPISchemaInput createInstanceInput = new InputNRRidPTidPIidPISchemaInput();
+ createInstanceInput.setNearRtRicId(NEAR_RT_RIC_ID);
+ createInstanceInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+ createInstanceInput.setPolicyInstanceId(policyInstanceId);
+ createInstanceInput.setPolicyInstance(instance);
+ InputNRRidPTidPIidPISchema inputSchema = new InputNRRidPTidPIidPISchema();
+ inputSchema.setInput(createInstanceInput);
+ a1ControllerApi.a1ControllerCreatePolicyInstance(inputSchema);
+ }
+
+ @ApiOperation(value = "Deletes the policy instances for the given policy type.")
+ @DeleteMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+ + POLICY_INSTANCE_ID_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
+ public void deletePolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+ @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId) {
+ logger.debug("deletePolicyInstance typeId: {}, instanceId: {}", policyTypeIdString, policyInstanceId);
+ InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+ instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+ instanceIdInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+ instanceIdInput.setPolicyInstanceId(policyInstanceId);
+ InputNRRidPTidPIidSchema inputSchema = new InputNRRidPTidPIidSchema();
+ inputSchema.setInput(instanceIdInput);
+ a1ControllerApi.a1ControllerDeletePolicyInstance(inputSchema);
+ }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
new file mode 100644
index 0000000..f5ecd10
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
@@ -0,0 +1,82 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.model.ErrorTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestClientResponseException;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+/**
+ * Catches certain exceptions. This controller advice factors out try-catch
+ * blocks in many controller methods.
+ *
+ * Also see:<br>
+ * https://www.baeldung.com/exception-handling-for-rest-with-spring
+ * https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services
+ */
+@ControllerAdvice
+public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
+
+ // Superclass has "logger" that is exposed here, so use a different name
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Logs the error and generates a JSON response when a REST controller method
+ * takes a RestClientResponseException. This is thrown by the Http client when a
+ * remote method returns a non-2xx code. All the controller methods are proxies
+ * in that they just forward the request along to a remote system, so if that
+ * remote system fails, return 502 plus some details about the failure, rather
+ * than the generic 500 that Spring-Boot will return on an uncaught exception.
+ *
+ * Why 502? I quote: <blockquote>HTTP server received an invalid response from a
+ * server it consulted when acting as a proxy or gateway.</blockquote>
+ *
+ * @param ex
+ * The exception
+ *
+ * @param request
+ * The original request
+ *
+ * @return A response entity with status code 502 plus some details in the body.
+ */
+ @ExceptionHandler({ RestClientResponseException.class })
+ public final ResponseEntity<ErrorTransport> handleProxyMethodException(Exception ex, WebRequest request) {
+ // Capture the full stack trace in the log.
+ log.error("handleProxyMethodException: request {}, exception {}", request.getDescription(false), ex);
+ if (ex instanceof HttpStatusCodeException) {
+ HttpStatusCodeException hsce = (HttpStatusCodeException) ex;
+ return new ResponseEntity<>(new ErrorTransport(hsce.getRawStatusCode(), hsce.getResponseBodyAsString(),
+ ex.toString(), request.getDescription(false)), HttpStatus.BAD_GATEWAY);
+ } else {
+ return new ResponseEntity<>(new ErrorTransport(500, ex), HttpStatus.BAD_GATEWAY);
+ }
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java
new file mode 100644
index 0000000..ed2e905
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java
@@ -0,0 +1,67 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Listens for requests to known Angular routes.
+ */
+@Controller
+public class Html5PathsController {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Forwards the browser to the index (main) page upon request of a known route.
+ * This unfortunately requires duplication of the Angular route strings in the
+ * path mappings on this method. Could switch to a regex pattern instead someday
+ * if the routes change too often.
+ *
+ * https://stackoverflow.com/questions/44692781/configure-spring-boot-to-redirect-404-to-a-single-page-app
+ *
+ * @param request
+ * HttpServletRequest
+ * @param response
+ * HttpServletResponse
+ * @throws IOException
+ * On error
+ */
+ @RequestMapping(method = { RequestMethod.OPTIONS, RequestMethod.GET }, //
+ path = { "/catalog", "/control", "/stats", "/user" })
+ public void forwardAngularRoutes(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ URL url = new URL(request.getScheme(), request.getServerName(), request.getServerPort(), "/index.html");
+ if (logger.isDebugEnabled())
+ logger.debug("forwardAngularRoutes: {} redirected to {}", request.getRequestURI(), url);
+ response.sendRedirect(url.toString());
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java
new file mode 100644
index 0000000..e2248e6
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java
@@ -0,0 +1,93 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * Provides a controller which is invoked on any error within the Spring-managed
+ * context, including page not found, and redirects the caller to a custom error
+ * page. The caller is also redirected to this page if a REST controller takes
+ * an uncaught exception.
+ *
+ * If trace is requested via request parameter ("?trace=true") and available,
+ * adds stack trace information to the standard JSON error response.
+ *
+ * Excluded from Swagger API documentation.
+ *
+ * https://stackoverflow.com/questions/25356781/spring-boot-remove-whitelabel-error-page
+ * https://www.baeldung.com/spring-boot-custom-error-page
+ */
+
+@ApiIgnore
+@Controller
+@RequestMapping(value = SimpleErrorController.ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class SimpleErrorController implements ErrorController {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String ERROR_PATH = "/error";
+
+ private final ErrorAttributes errorAttributes;
+
+ @Autowired
+ public SimpleErrorController(ErrorAttributes errorAttributes) {
+ this.errorAttributes = errorAttributes;
+ }
+
+ @Override
+ public String getErrorPath() {
+ logger.warn("getErrorPath");
+ return ERROR_PATH;
+ }
+
+ @GetMapping
+ public String handleError(HttpServletRequest request) {
+ ServletWebRequest servletWebRequest = new ServletWebRequest(request);
+ Throwable t = errorAttributes.getError(servletWebRequest);
+ if (t != null)
+ logger.warn("handleError", t);
+ Map<String, Object> attributes = errorAttributes.getErrorAttributes(servletWebRequest, true);
+ attributes.forEach((attribute, value) -> {
+ logger.warn("handleError: {} -> {}", attribute, value);
+ });
+ // Return the name of the page INCLUDING suffix, which I guess is a "view" name.
+ // Just "error" is not enough, but don't seem to need a ModelAndView object.
+ return "error.html";
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java
new file mode 100644
index 0000000..3a75dfa
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.k8sapi;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.util.HttpsURLConnectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+public class CaasIngressDemo {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static void main(String[] args) throws Exception {
+ HttpsURLConnectionUtils.turnOffSslChecking();
+ // Get IP address from REC deployment team for testing
+ final String podsUrl = "https://10.0.0.1:16443/api/v1/namespaces/ricaux/pods";
+ RestTemplate rt = new RestTemplate();
+ ResponseEntity<String> podsResponse = rt.getForEntity(podsUrl, String.class);
+ logger.info(podsResponse.getBody());
+ HttpsURLConnectionUtils.turnOnSslChecking();
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java
new file mode 100644
index 0000000..bcfae62
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.k8sapi;
+
+import java.lang.invoke.MethodHandles;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+
+/**
+ * Provides a method to get a list of pods using RestTemplate. As currently
+ * configured this uses the default JVM HTTPS support which can be configured to
+ * ignore errors as a workaround for self-signed SSL certificates.
+ */
+public class SimpleKubernetesClient {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final String k8sUrl;
+
+ public SimpleKubernetesClient(String baseUrl) {
+ logger.debug("ctor: baseUrl {}", baseUrl);
+ k8sUrl = baseUrl;
+ }
+
+ public String listPods(String namespace) {
+ logger.debug("listPods for namespace {}", namespace);
+ String podsUrl = new DefaultUriBuilderFactory(k8sUrl.trim()).builder().pathSegment("v1")
+ .pathSegment("namespaces").pathSegment(namespace.trim()).pathSegment("pods").build().normalize()
+ .toString();
+ RestTemplate rt = new RestTemplate();
+ ResponseEntity<String> podsResponse = rt.getForEntity(podsUrl, String.class);
+ return podsResponse.getBody();
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
new file mode 100644
index 0000000..7bc9f00
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
@@ -0,0 +1,85 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public class EcompUserDetails implements UserDetails {
+
+ private static final long serialVersionUID = 1L;
+ private final EcompUser ecompUser;
+
+ // This is the default Spring role-name prefix.
+ private static final String ROLEP = "ROLE_";
+
+ public EcompUserDetails(EcompUser ecompUser) {
+ this.ecompUser = ecompUser;
+ }
+
+ /*
+ * Gets a list of authorities (roles) for this user. To keep Spring happy, every
+ * item has prefix ROLE_.
+ */
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ List<GrantedAuthority> roleList = new ArrayList<>();
+ Iterator<EcompRole> roleIter = ecompUser.getRoles().iterator();
+ while (roleIter.hasNext()) {
+ EcompRole role = roleIter.next();
+ // Add the prefix if the ONAP portal doesn't supply it.
+ final String roleName = role.getName().startsWith(ROLEP) ? role.getName() : ROLEP + role.getName();
+ roleList.add(new SimpleGrantedAuthority(roleName));
+ }
+ return roleList;
+ }
+
+ public String getPassword() {
+ return null;
+ }
+
+ public String getUsername() {
+ return ecompUser.getLoginId();
+ }
+
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ public boolean isEnabled() {
+ return ecompUser.isActive();
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
new file mode 100644
index 0000000..516e3c8
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
@@ -0,0 +1,133 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.time.Instant;
+
+/**
+ * This mimics the model Spring-Boot uses for a message returned on failure, to
+ * be serialized as JSON.
+ */
+public class ErrorTransport implements IDashboardResponse {
+
+ private Instant timestamp;
+ private Integer status;
+ private String error;
+ private String message;
+ private String path;
+
+ /**
+ * Builds an empty object.
+ */
+ public ErrorTransport() {
+ // no-arg constructor
+ }
+
+ /**
+ * Convenience constructor for minimal value set.
+ *
+ * @param status
+ * Integer value like 400
+ * @param error
+ * Error message
+ */
+ public ErrorTransport(int status, String error) {
+ this(status, error, null, null);
+ }
+
+ /**
+ * Convenience constructor for populating an error from an exception
+ *
+ * @param status
+ * Integer value like 400
+ * @param throwable
+ * The caught exception/throwable to convert to String with
+ * an upper bound on characters
+ */
+ public ErrorTransport(int status, Throwable throwable) {
+ this.timestamp = Instant.now();
+ this.status = status;
+ final int enough = 256;
+ String exString = throwable.toString();
+ this.error = exString.length() > enough ? exString.substring(0, enough) : exString;
+ }
+
+ /**
+ * Builds an object with all fields
+ *
+ * @param status
+ * Integer value like 500
+ * @param error
+ * Explanation
+ * @param message
+ * Additional explanation
+ * @param path
+ * Requested path
+ */
+ public ErrorTransport(int status, String error, String message, String path) {
+ this.timestamp = Instant.now();
+ this.status = status;
+ this.error = error;
+ this.message = message;
+ this.path = path;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String error) {
+ this.message = error;
+ }
+
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Instant timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java
new file mode 100644
index 0000000..a298fa6
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java
@@ -0,0 +1,27 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+/**
+ * Marker interface used by all transport models.
+ */
+public interface IDashboardResponse {
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java
new file mode 100644
index 0000000..b699b3c
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java
@@ -0,0 +1,48 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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;
+
+public class PolicyInstance implements IDashboardResponse {
+ private String instanceId;
+ private Object instance;
+
+ public PolicyInstance(String id, Object instance) {
+ this.instanceId = id;
+ this.instance = instance;
+ }
+
+ public String getInstanceId() {
+ return instanceId;
+ }
+ public void setInstanceId(String instanceId) {
+ this.instanceId = instanceId;
+ }
+ public Object getInstance() {
+ return instance;
+ }
+ public void setInstance(Object instance) {
+ this.instance = instance;
+ }
+
+ @Override
+ public String toString() {
+ return PolicyInstance.class.getName() + ": [id:" + instanceId + ", instance: " + instance + "]";
+ }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java
new file mode 100644
index 0000000..c750487
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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;
+
+public class PolicyInstances extends ArrayList<PolicyInstance> {
+
+ private static final long serialVersionUID = -928428052502491021L;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java
new file mode 100644
index 0000000..d24667a
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java
@@ -0,0 +1,81 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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 com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PolicyType {
+
+ @JsonProperty("policy_type_id")
+ Integer policyTypeId;
+
+ @JsonProperty("name")
+ String name;
+
+ @JsonProperty("description")
+ String description;
+
+ @JsonProperty("create_schema")
+ String createSchema;
+
+ public PolicyType(Integer policyId, String name, String description, String createSchema) {
+ this.policyTypeId = policyId;
+ this.name = name;
+ this.description = description;
+ this.createSchema = createSchema;
+ }
+
+ public Integer getPolicyTypeId() {
+ return policyTypeId;
+ }
+
+ public void setPolicyTypeId(Integer policyTypeId) {
+ this.policyTypeId = policyTypeId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getCreateSchema() {
+ return createSchema;
+ }
+
+ public void setCreateSchema(String createSchema) {
+ this.createSchema = createSchema;
+ }
+
+ @Override
+ public String toString() {
+ return "[policy_type_id:" + policyTypeId + ", name:" + name + ", description:" + description + ", create_schema:" + createSchema + "]";
+ }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java
new file mode 100644
index 0000000..3165d1b
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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;
+
+public class PolicyTypes extends ArrayList<PolicyType> {
+
+ private static final long serialVersionUID = -928428052502491021L;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java
new file mode 100644
index 0000000..9e13789
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java
@@ -0,0 +1,63 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+public class SuccessTransport implements IDashboardResponse {
+
+ private int status;
+ private Object data;
+
+ /**
+ * Builds an empty object
+ */
+ public SuccessTransport() {
+ // no-arg constructor
+ }
+
+ /**
+ * Builds an object with the specified values.
+ *
+ * @param status
+ * Status code
+ * @param data
+ * Data to transport
+ */
+ public SuccessTransport(int status, Object data) {
+ this.status = status;
+ this.data = data;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public Object getData() {
+ return data;
+ }
+
+ public void setData(Object data) {
+ this.data = data;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
new file mode 100644
index 0000000..34d80c9
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+
+/**
+ * Supports an upgrade path among methods in CipherUtil because the PortalSDK is
+ * changing encryption methods.
+ */
+public interface IPortalSdkDecryptor {
+
+ /**
+ * Decrypts the specified value using a known key.
+ *
+ * @param cipherText
+ * Encrypted value
+ * @return Clear text on success, null otherwise.
+ * @throws CipherUtilException
+ * if any decryption step fails
+ */
+ String decrypt(String cipherText) throws CipherUtilException;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
new file mode 100644
index 0000000..dc70f7e
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
@@ -0,0 +1,120 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides services to authenticate requests from/to ONAP Portal.
+ */
+public class PortalAuthManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ final Map<String, String> credentialsMap;
+ private final IPortalSdkDecryptor portalSdkDecryptor;
+ private final String userIdCookieName;
+
+ public PortalAuthManager(final String appName, final String username, final String password,
+ final String decryptorClassName, final String userCookie)
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException, SecurityException {
+ credentialsMap = new HashMap<>();
+ credentialsMap.put(IPortalRestCentralService.CREDENTIALS_APP, appName);
+ credentialsMap.put(IPortalRestCentralService.CREDENTIALS_USER, username);
+ credentialsMap.put(IPortalRestCentralService.CREDENTIALS_PASS, password);
+ this.userIdCookieName = userCookie;
+ // Instantiate here so configuration errors are detected at app-start time
+ logger.debug("ctor: using decryptor class {}", decryptorClassName);
+ Class<?> decryptorClass = Class.forName(decryptorClassName);
+ portalSdkDecryptor = (IPortalSdkDecryptor) decryptorClass.getDeclaredConstructor().newInstance();
+ }
+
+ /**
+ * @return A map of key-value pairs with application name, user name and
+ * password.
+ */
+ public Map<String, String> getAppCredentials() {
+ return credentialsMap;
+ }
+
+ /**
+ * Searches the request for a cookie with the specified name.
+ *
+ * @param request
+ * HttpServletRequest
+ * @param cookieName
+ * Cookie name
+ * @return Cookie, or null if not found.
+ */
+ private Cookie getCookie(HttpServletRequest request, String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
+ for (Cookie cookie : cookies)
+ if (cookie.getName().equals(cookieName))
+ return cookie;
+ return null;
+ }
+
+ /**
+ * Validates whether the ECOMP Portal sign-on process has completed. Checks for
+ * the ECOMP cookie first, then the user cookie.
+ *
+ * @param request
+ * HttpServletRequest
+ * @return User ID if the ECOMP cookie is present and the sign-on process
+ * established a user ID; else null.
+ */
+ public String validateEcompSso(HttpServletRequest request) {
+ // Check ECOMP Portal cookie
+ Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+ if (ep == null) {
+ logger.debug("validateEcompSso: cookie not found: {}", PortalApiConstants.EP_SERVICE);
+ return null;
+ }
+ logger.trace("validateEcompSso: found cookie {}", PortalApiConstants.EP_SERVICE);
+ Cookie user = getCookie(request, userIdCookieName);
+ if (user == null) {
+ logger.debug("validateEcompSso: cookie not found: {}", userIdCookieName);
+ return null;
+ }
+ logger.trace("validateEcompSso: user cookie {}", userIdCookieName);
+ String userid = null;
+ try {
+ userid = portalSdkDecryptor.decrypt(user.getValue());
+ } catch (CipherUtilException e) {
+ throw new IllegalArgumentException("validateEcompSso failed", e);
+ }
+ return userid;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
new file mode 100644
index 0000000..4b6de91
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
@@ -0,0 +1,281 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.invoke.MethodHandles;
+import java.net.URLEncoder;
+import java.util.HashSet;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.onap.portalsdk.core.onboarding.util.KeyProperties;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.model.EcompUserDetails;
+import org.owasp.esapi.reference.DefaultSecurityConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+/**
+ * This filter checks every request for the cookie set by the ONAP Portal single
+ * sign on process. The possible paths and actions:
+ * <OL>
+ * <LI>User starts at an app page via a bookmark. No Portal cookie is set.
+ * Redirect there to get one; then continue as below.
+ * <LI>User starts at Portal and goes to app. Alternately, the user's session
+ * times out and the user hits refresh. The Portal cookie is set, but there is
+ * no valid session. Create one and publish info.
+ * <LI>User has valid Portal cookie and session. Reset the max idle in that
+ * session.
+ * </OL>
+ * <P>
+ * Notes:
+ * <UL>
+ * <LI>While redirecting, the cookie "redirectUrl" should also be set so that
+ * Portal knows where to forward the request to once the Portal Session is
+ * created and EPService cookie is set.
+ * </UL>
+ *
+ * TODO: What about sessions? Will this be stateless?
+ *
+ * This filter uses no annotations to avoid Spring's automatic registration,
+ * which add this filter in the chain in the wrong order.
+ */
+public class PortalAuthenticationFilter implements Filter {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Unfortunately not all file names are defined as constants
+ private static final String[] securityPropertyFiles = { KeyProperties.PROPERTY_FILE_NAME,
+ PortalApiProperties.PROPERTY_FILE_NAME, DefaultSecurityConfiguration.DEFAULT_RESOURCE_FILE,
+ "validation.properties" };
+
+ public static final String REDIRECT_URL_KEY = "redirectUrl";
+
+ private final boolean enforcePortalSecurity;
+ private final PortalAuthManager authManager;
+
+ private final DashboardUserManager userManager;
+
+ public PortalAuthenticationFilter(boolean portalSecurity, PortalAuthManager authManager,
+ DashboardUserManager userManager) {
+ this.enforcePortalSecurity = portalSecurity;
+ this.authManager = authManager;
+ this.userManager = userManager;
+ if (portalSecurity) {
+ // Throw if security is requested and prerequisites are not met
+ for (String pf : securityPropertyFiles) {
+ InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
+ if (in == null) {
+ String msg = "Failed to find property file on classpath: " + pf;
+ logger.error(msg);
+ throw new RuntimeException(msg);
+ } else {
+ try {
+ in.close();
+ } catch (IOException ex) {
+ logger.warn("Failed to close stream", ex);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // complain loudly if this key property is missing
+ String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+ logger.debug("init: Portal redirect URL {}", url);
+ if (url == null)
+ logger.error(
+ "init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
+ }
+
+ @Override
+ public void destroy() {
+ // No resources to release
+ }
+
+ /**
+ * Requests for pages ignored in the web security config do not hit this filter.
+ */
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ if (enforcePortalSecurity)
+ doFilterEPSDKFW(req, res, chain);
+ else
+ doFilterMockUserAdminRole(req, res, chain);
+ }
+
+ /*
+ * Populates security context with a mock user in the admin role.
+ *
+ */
+ private void doFilterMockUserAdminRole(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth == null || auth.getAuthorities().isEmpty()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("doFilter adding auth to request URI {}",
+ (req instanceof HttpServletRequest) ? ((HttpServletRequest) req).getRequestURL() : req);
+ }
+ EcompRole admin = new EcompRole();
+ admin.setId(1L);
+ admin.setName(DashboardConstants.ROLE_ADMIN);
+ HashSet<EcompRole> roles = new HashSet<>();
+ roles.add(admin);
+ EcompUser user = new EcompUser();
+ user.setLoginId("fakeLoginId");
+ user.setRoles(roles);
+ user.setActive(true);
+ EcompUserDetails userDetails = new EcompUserDetails(user);
+ PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
+ "fakeCredentials", userDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ } else {
+ logger.debug("doFilter: authorities {}", auth.getAuthorities());
+ }
+ chain.doFilter(req, res);
+ }
+
+ /*
+ * Checks for valid cookies and allows request to be served if found; redirects
+ * to Portal otherwise.
+ */
+ private void doFilterEPSDKFW(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+ if (logger.isTraceEnabled())
+ logger.trace("doFilter: req {}", request.getRequestURI());
+ // Need to authenticate the request
+ final String userId = authManager.validateEcompSso(request);
+ final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
+ if (userId == null || ecompUser == null) {
+ logger.debug("doFilter: unauthorized user requests URI {}, serving login page", request.getRequestURI());
+ StringBuffer sb = request.getRequestURL();
+ sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
+ String body = generateLoginRedirectPage(sb.toString());
+ response.setContentType(MediaType.TEXT_HTML_VALUE);
+ response.getWriter().print(body);
+ response.getWriter().flush();
+ } else {
+ EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
+ // Using portal session as credentials is a hack
+ PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
+ getPortalSessionId(request), userDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ // Pass request back down the filter chain
+ chain.doFilter(request, response);
+ }
+ }
+
+ /**
+ * Generates a page with text only, absolutely no references to any webapp
+ * resources, so this can be served to an unauthenticated user without
+ * triggering a new authentication attempt. The page has a link to the Portal
+ * URL from configuration, with a return URL that is the original request.
+ *
+ * @param appUrl
+ * Original requested URL
+ * @return HTML
+ * @throws UnsupportedEncodingException
+ * On error
+ */
+ private static String generateLoginRedirectPage(String appUrl) throws UnsupportedEncodingException {
+ String encodedAppUrl = URLEncoder.encode(appUrl, "UTF-8");
+ String portalBaseUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+ String redirectUrl = portalBaseUrl + "?" + PortalAuthenticationFilter.REDIRECT_URL_KEY + "=" + encodedAppUrl;
+ String aHref = "<a href=\"" + redirectUrl + "\">";
+ // If only Java had "here" documents.
+ String body = String.join(//
+ System.getProperty("line.separator"), //
+ "<html>", //
+ "<head>", //
+ "<title>RIC Dashboard</title>", //
+ "<style>", //
+ "html, body { ", //
+ " font-family: Helvetica, Arial, sans-serif;", //
+ "}", //
+ "</style>", //
+ "</head>", //
+ "<body>", //
+ "<h2>RIC Dashboard</h2>", //
+ "<h4>Please log in.</h4>", //
+ "<p>", //
+ aHref, "Click here to authenticate at the ONAP Portal</a>", //
+ "</p>", //
+ "</body>", //
+ "</html>");
+ return body;
+ }
+
+ /**
+ * Searches the request for a cookie with the specified name.
+ *
+ * @param request
+ * HttpServletRequest
+ * @param cookieName
+ * Cookie name
+ * @return Cookie, or null if not found.
+ */
+ private Cookie getCookie(HttpServletRequest request, String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
+ for (Cookie cookie : cookies)
+ if (cookie.getName().equals(cookieName))
+ return cookie;
+ return null;
+ }
+
+ /**
+ * Gets the ECOMP Portal service cookie value.
+ *
+ * @param request
+ * @return Cookie value, or null if not found.
+ */
+ private String getPortalSessionId(HttpServletRequest request) {
+ Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+ if (ep == null)
+ return null;
+ return ep.getValue();
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
new file mode 100644
index 0000000..581ca25
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
@@ -0,0 +1,89 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.config.SpringContextCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Implements the contract used by the Portal to transmit user details to this
+ * on-boarded application. The requests are intercepted first by a servlet in
+ * the EPSDK-FW library, which proxies the calls to these methods.
+ *
+ * An instance of this class is created upon first request to the API. But this
+ * class is found and instantiated via Class.forName(), so cannot use Spring
+ * annotations.
+ */
+public class PortalRestCentralServiceImpl implements IPortalRestCentralService {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final PortalAuthManager authManager;
+ private final DashboardUserManager userManager;
+
+ public PortalRestCentralServiceImpl() throws IOException, PortalAPIException {
+ final ApplicationContext context = SpringContextCache.getApplicationContext();
+ authManager = (PortalAuthManager) context.getBean(PortalAuthManager.class);
+ userManager = (DashboardUserManager) context.getBean(DashboardUserManager.class);
+ }
+
+ /*
+ * Answers the Portal API credentials.
+ */
+ @Override
+ public Map<String, String> getAppCredentials() throws PortalAPIException {
+ logger.debug("getAppCredentials");
+ return authManager.getAppCredentials();
+ }
+
+ /*
+ * Extracts the user ID from a cookie in the header
+ */
+ @Override
+ public String getUserId(HttpServletRequest request) throws PortalAPIException {
+ logger.debug("getuserId");
+ return authManager.validateEcompSso(request);
+ }
+
+ @Override
+ public void pushUser(EcompUser user) throws PortalAPIException {
+ logger.debug("pushUser: {}", user);
+ userManager.createUser(user);
+ }
+
+ @Override
+ public void editUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: {}", user);
+ userManager.updateUser(loginId, user);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
new file mode 100644
index 0000000..279f1dd
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorAes implements IPortalSdkDecryptor {
+
+ @SuppressWarnings("deprecation")
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decrypt(cipherText);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
new file mode 100644
index 0000000..0127267
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorPkc implements IPortalSdkDecryptor {
+
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decryptPKC(cipherText);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java
new file mode 100644
index 0000000..a97ed7b
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java
@@ -0,0 +1,82 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.util;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Disables and enables certificate and host-name checking in
+ * HttpsURLConnection, the default JVM implementation of the HTTPS/TLS protocol.
+ * Has no effect on implementations such as Apache Http Client, Ok Http.
+ *
+ * https://stackoverflow.com/questions/23504819/how-to-disable-ssl-certificate-checking-with-spring-resttemplate/58291331#58291331
+ */
+public final class HttpsURLConnectionUtils {
+
+ private static final HostnameVerifier jvmHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+
+ private static final HostnameVerifier trivialHostnameVerifier = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return true;
+ }
+ };
+
+ private static final TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[] { new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ } };
+
+ public static void turnOffSslChecking() throws NoSuchAlgorithmException, KeyManagementException {
+ HttpsURLConnection.setDefaultHostnameVerifier(trivialHostnameVerifier);
+ // Install the all-trusting trust manager
+ SSLContext sc = SSLContext.getInstance("SSL");
+ sc.init(null, UNQUESTIONING_TRUST_MANAGER, null);
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+
+ public static void turnOnSslChecking() throws KeyManagementException, NoSuchAlgorithmException {
+ HttpsURLConnection.setDefaultHostnameVerifier(jvmHostnameVerifier);
+ // Return it to the initial state (discovered by reflection, now hardcoded)
+ SSLContext sc = SSLContext.getInstance("SSL");
+ sc.init(null, null, null);
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+
+ private HttpsURLConnectionUtils() {
+ throw new UnsupportedOperationException("Do not instantiate libraries.");
+ }
+}
diff --git a/dashboard/webapp-backend/src/main/resources/ESAPI.properties b/dashboard/webapp-backend/src/main/resources/ESAPI.properties
new file mode 100644
index 0000000..9a26119
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/resources/ESAPI.properties
@@ -0,0 +1,385 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+#===========================================================================
+# ESAPI Configuration
+#
+# If true, then print all the ESAPI properties set here when they are loaded.
+# If false, they are not printed. Useful to reduce output when running JUnit tests.
+# If you need to troubleshoot a properties related problem, turning this on may help.
+# This is 'false' in the src/test/resources/.esapi version. It is 'true' by
+# default for reasons of backward compatibility with earlier ESAPI versions.
+ESAPI.printProperties=false
+
+# ESAPI is designed to be easily extensible. You can use the reference implementation
+# or implement your own providers to take advantage of your enterprise's security
+# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like:
+#
+# String ciphertext =
+# ESAPI.encryptor().encrypt("Secret message"); // Deprecated in 2.0
+# CipherText cipherText =
+# ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred
+#
+# Below you can specify the classname for the provider that you wish to use in your
+# application. The only requirement is that it implement the appropriate ESAPI interface.
+# This allows you to switch security implementations in the future without rewriting the
+# entire application.
+#
+# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory
+ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController
+# FileBasedAuthenticator requires users.txt file in .esapi directory
+ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator
+ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
+ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor
+
+ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor
+ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities
+ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector
+#ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory
+ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer
+ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator
+
+#===========================================================================
+# ESAPI Authenticator
+#
+Authenticator.AllowedLoginAttempts=3
+#Authenticator.MaxOldPasswordHashes=13
+Authenticator.UsernameParameterName=username
+#Authenticator.PasswordParameterName=password
+# RememberTokenDuration (in days)
+Authenticator.RememberTokenDuration=14
+# Session Timeouts (in minutes)
+Authenticator.IdleTimeoutDuration=20
+Authenticator.AbsoluteTimeoutDuration=120
+
+#===========================================================================
+# ESAPI Encoder
+#
+# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks.
+# Failure to canonicalize input is a very common mistake when implementing validation schemes.
+# Canonicalization is automatic when using the ESAPI Validator, but you can also use the
+# following code to canonicalize data.
+#
+# ESAPI.Encoder().canonicalize( "%22hello world"" );
+#
+# Multiple encoding is when a single encoding format is applied multiple times. Allowing
+# multiple encoding is strongly discouraged.
+Encoder.AllowMultipleEncoding=false
+
+# Mixed encoding is when multiple different encoding formats are applied, or when
+# multiple formats are nested. Allowing multiple encoding is strongly discouraged.
+Encoder.AllowMixedEncoding=false
+
+# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs
+# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or
+# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important.
+Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec
+
+
+#===========================================================================
+# ESAPI Encryption
+#
+# The ESAPI Encryptor provides basic cryptographic functions with a simplified API.
+# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+# There is not currently any support for key rotation, so be careful when changing your key and salt as it
+# will invalidate all signed, encrypted, and hashed data.
+#
+# WARNING: Not all combinations of algorithms and key lengths are supported.
+# If you choose to use a key length greater than 128, you MUST download the
+# unlimited strength policy files and install in the lib directory of your JRE/JDK.
+# See http://java.sun.com/javase/downloads/index.jsp for more information.
+#
+# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API
+# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever
+# possible, these methods should be avoided as they use ECB cipher mode, which in almost
+# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default
+# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0. In general, you
+# should only use this compatibility setting if you have persistent data encrypted with
+# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL
+# you have decrypted all of your old encrypted data and then re-encrypted it with
+# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode
+# with the new 2.0 methods, make sure that you use the same cipher algorithm for both
+# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for
+# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods
+# where you can specify a SecretKey. (Note that if you are using the 256-bit AES,
+# that requires downloading the special jurisdiction policy files mentioned above.)
+#
+# ***** IMPORTANT: Do NOT forget to replace these with your own values! *****
+# To calculate these values, you can run:
+# java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+#
+Encryptor.MasterKey=tzfztf56ftv
+Encryptor.MasterSalt=123456ztrewq
+
+# Provides the default JCE provider that ESAPI will "prefer" for its symmetric
+# encryption and hashing. (That is it will look to this provider first, but it
+# will defer to other providers if the requested algorithm is not implemented
+# by this provider.) If left unset, ESAPI will just use your Java VM's current
+# preferred JCE provider, which is generally set in the file
+# "$JAVA_HOME/jre/lib/security/java.security".
+#
+# The main intent of this is to allow ESAPI symmetric encryption to be
+# used with a FIPS 140-2 compliant crypto-module. For details, see the section
+# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in
+# the ESAPI 2.0 Symmetric Encryption User Guide, at:
+# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html
+# However, this property also allows you to easily use an alternate JCE provider
+# such as "Bouncy Castle" without having to make changes to "java.security".
+# See Javadoc for SecurityProviderLoader for further details. If you wish to use
+# a provider that is not known to SecurityProviderLoader, you may specify the
+# fully-qualified class name of the JCE provider class that implements
+# java.security.Provider. If the name contains a '.', this is interpreted as
+# a fully-qualified class name that implements java.security.Provider.
+#
+# NOTE: Setting this property has the side-effect of changing it in your application
+# as well, so if you are using JCE in your application directly rather than
+# through ESAPI (you wouldn't do that, would you? ;-), it will change the
+# preferred JCE provider there as well.
+#
+# Default: Keeps the JCE provider set to whatever JVM sets it to.
+Encryptor.PreferredJCEProvider=
+
+# AES is the most widely used and strongest encryption algorithm. This
+# should agree with your Encryptor.CipherTransformation property.
+# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is
+# very weak. It is essentially a password-based encryption key, hashed
+# with MD5 around 1K times and then encrypted with the weak DES algorithm
+# (56-bits) using ECB mode and an unspecified padding (it is
+# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses
+# "AES/CBC/PKCSPadding". If you want to change these, change them here.
+# Warning: This property does not control the default reference implementation for
+# ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped
+# in the future.
+# @deprecated
+Encryptor.EncryptionAlgorithm=AES
+# For ESAPI Java 2.0 - New encrypt / decrypt methods use this.
+Encryptor.CipherTransformation=AES/CBC/PKCS5Padding
+
+# Applies to ESAPI 2.0 and later only!
+# Comma-separated list of cipher modes that provide *BOTH*
+# confidentiality *AND* message authenticity. (NIST refers to such cipher
+# modes as "combined modes" so that's what we shall call them.) If any of these
+# cipher modes are used then no MAC is calculated and stored
+# in the CipherText upon encryption. Likewise, if one of these
+# cipher modes is used with decryption, no attempt will be made
+# to validate the MAC contained in the CipherText object regardless
+# of whether it contains one or not. Since the expectation is that
+# these cipher modes support support message authenticity already,
+# injecting a MAC in the CipherText object would be at best redundant.
+#
+# Note that as of JDK 1.5, the SunJCE provider does not support *any*
+# of these cipher modes. Of these listed, only GCM and CCM are currently
+# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports
+# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other
+# padding modes.
+Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC
+
+# Applies to ESAPI 2.0 and later only!
+# Additional cipher modes allowed for ESAPI 2.0 encryption. These
+# cipher modes are in _addition_ to those specified by the property
+# 'Encryptor.cipher_modes.combined_modes'.
+# Note: We will add support for streaming modes like CFB & OFB once
+# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod'
+# (probably in ESAPI 2.1).
+# DISCUSS: Better name?
+Encryptor.cipher_modes.additional_allowed=CBC
+
+# 128-bit is almost always sufficient and appears to be more resistant to
+# related key attacks than is 256-bit AES. Use '_' to use default key size
+# for cipher algorithms (where it makes sense because the algorithm supports
+# a variable key size). Key length must agree to what's provided as the
+# cipher transformation, otherwise this will be ignored after logging a
+# warning.
+#
+# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing!
+Encryptor.EncryptionKeyLength=128
+
+# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV).
+# (All cipher modes except ECB require an IV.) There are two choices: we can either
+# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While
+# the IV does not need to be hidden from adversaries, it is important that the
+# adversary not be allowed to choose it. Also, random IVs are generally much more
+# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes
+# such as CFB and OFB use a different IV for each encryption with a given key so
+# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random
+# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and
+# uncomment the Encryptor.fixedIV.
+#
+# Valid values: random|fixed|specified 'specified' not yet implemented; planned for 2.1
+Encryptor.ChooseIVMethod=random
+# If you choose to use a fixed IV, then you must place a fixed IV here that
+# is known to all others who are sharing your secret key. The format should
+# be a hex string that is the same length as the cipher block size for the
+# cipher algorithm that you are using. The following is an *example* for AES
+# from an AES test vector for AES-128/CBC as described in:
+# NIST Special Publication 800-38A (2001 Edition)
+# "Recommendation for Block Cipher Modes of Operation".
+# (Note that the block size for AES is 16 bytes == 128 bits.)
+#
+Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f
+
+# Whether or not CipherText should use a message authentication code (MAC) with it.
+# This prevents an adversary from altering the IV as well as allowing a more
+# fool-proof way of determining the decryption failed because of an incorrect
+# key being supplied. This refers to the "separate" MAC calculated and stored
+# in CipherText, not part of any MAC that is calculated as a result of a
+# "combined mode" cipher mode.
+#
+# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also
+# set this property to false.
+Encryptor.CipherText.useMAC=true
+
+# Whether or not the PlainText object may be overwritten and then marked
+# eligible for garbage collection. If not set, this is still treated as 'true'.
+Encryptor.PlainText.overwrite=true
+
+# Do not use DES except in a legacy situations. 56-bit is way too small key size.
+#Encryptor.EncryptionKeyLength=56
+#Encryptor.EncryptionAlgorithm=DES
+
+# TripleDES is considered strong enough for most purposes.
+# Note: There is also a 112-bit version of DESede. Using the 168-bit version
+# requires downloading the special jurisdiction policy from Sun.
+#Encryptor.EncryptionKeyLength=168
+#Encryptor.EncryptionAlgorithm=DESede
+
+Encryptor.HashAlgorithm=SHA-512
+Encryptor.HashIterations=1024
+Encryptor.DigitalSignatureAlgorithm=SHA1withDSA
+Encryptor.DigitalSignatureKeyLength=1024
+Encryptor.RandomAlgorithm=SHA1PRNG
+Encryptor.CharacterEncoding=UTF-8
+
+# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function
+# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and
+# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for
+# the MAC, mostly to keep the overall size at a minimum.)
+#
+# Currently supported choices for JDK 1.5 and 1.6 are:
+# HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and
+# HmacSHA512 (512 bits).
+# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though
+# the JDKs support it. See the ESAPI 2.0 Symmetric Encryption User Guide
+# further details.
+Encryptor.KDF.PRF=HmacSHA256
+#===========================================================================
+# ESAPI Logging
+# Set the application name if these logs are combined with other applications
+Logger.ApplicationName=portal_ric_dashboard
+# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true
+Logger.LogEncodingRequired=false
+# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.
+Logger.LogApplicationName=true
+# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.
+Logger.LogServerIP=true
+# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you
+# want to place it in a specific directory.
+Logger.LogFileName=portal_ric_dashboard_esapi_log
+# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)
+Logger.MaxLogFileSize=10000000
+
+
+#===========================================================================
+# ESAPI Intrusion Detection
+#
+# Each event has a base to which .count, .interval, and .action are added
+# The IntrusionException will fire if we receive "count" events within "interval" seconds
+# The IntrusionDetector is configurable to take the following actions: log, logout, and disable
+# (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable
+#
+# Custom Events
+# Names must start with "event." as the base
+# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here
+# You can also disable intrusion detection completely by changing
+# the following parameter to true
+#
+IntrusionDetector.Disable=false
+#
+IntrusionDetector.event.test.count=2
+IntrusionDetector.event.test.interval=10
+IntrusionDetector.event.test.actions=disable,log
+
+# Exception Events
+# All EnterpriseSecurityExceptions are registered automatically
+# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException
+# Use the fully qualified classname of the exception as the base
+
+# any intrusion is an attack
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout
+
+# for test purposes
+# CHECKME: Shouldn't there be something in the property name itself that designates
+# that these are for testing???
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout
+
+# rapid validation errors indicate scans or attacks in progress
+# org.owasp.esapi.errors.ValidationException.count=10
+# org.owasp.esapi.errors.ValidationException.interval=10
+# org.owasp.esapi.errors.ValidationException.actions=log,logout
+
+# sessions jumping between hosts indicates session hijacking
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout
+
+
+#===========================================================================
+# ESAPI Validation
+#
+# The ESAPI Validator works on regular expressions with defined names. You can define names
+# either here, or you may define application specific patterns in a separate file defined below.
+# This allows enterprises to specify both organizational standards as well as application specific
+# validation rules.
+#
+Validator.ConfigurationFile=validation.properties
+Validator.ConfigurationFile.MultiValued=false
+
+# Validators used by ESAPI
+Validator.AccountName=^[a-zA-Z0-9]{3,20}$
+Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$
+Validator.RoleName=^[a-z]{1,20}$
+
+#the word TEST below should be changed to your application
+#name - only relative URL's are supported
+Validator.Redirect=^\\/test.*$
+
+# Global HTTP Validation Rules
+# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]
+Validator.HTTPScheme=^(http|https)$
+Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$
+Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$
+Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$
+Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$
+Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$
+Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$
+Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPURL=^.*$
+Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$
+
+# Validation of file related input
+Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
+Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
diff --git a/dashboard/webapp-backend/src/main/resources/application.properties b/dashboard/webapp-backend/src/main/resources/application.properties
new file mode 100644
index 0000000..e969968
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/resources/application.properties
@@ -0,0 +1,73 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# Defines RIC Dashboard property keys and default values.
+# Create a copy in the launch directory to override values; or
+# copy to "application-abc.properties" as mentioned in the README.
+
+# A spring property but without a "spring" prefix;
+# the port number is chosen RANDOMLY when running tests
+server.port = 8080
+
+# path to file that stores user details;
+# use a persistent volume in a K8S deployment
+userfile = users.json
+
+# boolean flag whether to enforce Portal user and roles on requests
+portalapi.security = true
+# class that decrypts ciphertext from Portal
+portalapi.decryptor = org.oransc.ric.portal.dashboard.portalapi.PortalSdkDecryptorAes
+# name of request cookie with user ID
+portalapi.usercookie = UserId
+
+# portal credentials must be supplied at deployment time
+portalapi.appname = RIC Dashboard
+portalapi.username =
+portalapi.password =
+
+# endpoint URLs must be supplied at deployment time
+# A1 Mediator
+a1med.url.prefix = http://jar-app-props-default-A1-URL-prefix
+a1med.url.suffix =
+# endpoint URLs must be supplied at deployment time
+# A1 Controller
+a1controller.url.prefix = http://localhost:8282
+a1controller.url.suffix = /restconf/operations
+# ANR xApp
+anrxapp.url.prefix = http://jar-app-props-default-ANR-URL-prefix
+anrxapp.url.suffix =
+# App Manager
+appmgr.url.prefix = http://jar-app-props-default-Xapp-Mgr-URL
+appmgr.url.suffix = /ric/v1
+# E2 Manager
+e2mgr.url.prefix = http://jar-app-props-default-E2-URL
+e2mgr.url.suffix = /v1
+# Kubernetes API via https://github.com/nokia/caas-ingress
+# Set insecure=true to disable SSL certificate and hostname checking
+caasingress.insecure = true
+caasingress.aux.url.prefix = https://jar-app-props-default-caas-ingress-aux-URL
+caasingress.aux.url.suffix = /api
+caasingress.plt.url.prefix = https://jar-app-props-default-caas-ingress-plt-URL
+caasingress.plt.url.suffix = /api
+
+# Mimic slow endpoints by defining sleep period, in milliseconds
+mock.config.delay = 0
+
+# Kibana report on metrics
+metrics.url.ac = http://jar-app-props-kibana-url-ac
+metrics.url.mc = http://jar-app-props-kibana-url-mc
diff --git a/dashboard/webapp-backend/src/main/resources/logback.xml b/dashboard/webapp-backend/src/main/resources/logback.xml
new file mode 100644
index 0000000..213d105
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/resources/logback.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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===================================
+ -->
+
+<configuration>
+
+ <!-- Basic logback configuration for dev and test -->
+
+ <!-- component name is log file basename -->
+ <property name="componentName" value="ric-dashboard"></property>
+ <!-- gather files in a subdirectory -->
+ <property name="logDirectory" value="logs" />
+ <!-- output pattern -->
+ <property name="pattern" value="%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", UTC} [%thread] %-5level %logger{36} - %msg%n"/>
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- defaults to type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+ <encoder>
+ <pattern>${pattern}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${logDirectory}/${componentName}.log</file>
+ <append>true</append>
+ <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+ <fileNamePattern>${logDirectory}/${componentName}.%i.log.zip</fileNamePattern>
+ <minIndex>1</minIndex>
+ <maxIndex>9</maxIndex>
+ </rollingPolicy>
+ <triggeringPolicy
+ class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+ <maxFileSize>10MB</maxFileSize>
+ </triggeringPolicy>
+ <!-- defaults to type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+ <encoder>
+ <pattern>${pattern}</pattern>
+ </encoder>
+ </appender>
+
+ <!-- Set default level for all loggers -->
+ <root level="INFO">
+ <appender-ref ref="CONSOLE" />
+ <appender-ref ref="FILE" />
+ </root>
+
+ <!-- Code under test should be chatty --> >
+ <logger name="org.oransc.ric.portal.dashboard" level="DEBUG" />
+
+ <!-- Watch authentication done by EPSDK-FW -->
+ <logger name="org.onap.portalsdk.core.onboarding.crossapi" level="DEBUG" />
+
+ <!-- Report request URLs -->
+ <logger name="org.springframework.web.client.RestTemplate" level="DEBUG" />
+
+ <!-- for debugging security
+ <logger name="org.springframework.security" level="DEBUG" />
+ -->
+
+</configuration>
diff --git a/dashboard/webapp-backend/src/main/resources/static/error.html b/dashboard/webapp-backend/src/main/resources/static/error.html
new file mode 100644
index 0000000..1b7633f
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/resources/static/error.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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===================================
+ -->
+
+<html>
+<head>
+<title>Static error page</title>
+<style>
+html, body {
+ font-family: Helvetica, Arial, sans-serif;
+}
+</style>
+</head>
+<body>
+<h2>Non RT RIC Dashboard Error</h2>
+<h4>The previous request could not be processed.</h4>
+<a href="/">Click here to reload the application</a>
+</body>
+</html>
diff --git a/dashboard/webapp-backend/src/main/resources/validation.properties b/dashboard/webapp-backend/src/main/resources/validation.properties
new file mode 100644
index 0000000..c7a5c79
--- /dev/null
+++ b/dashboard/webapp-backend/src/main/resources/validation.properties
@@ -0,0 +1,19 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# empty file to suppress OWASP complaints emitted to stdout
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java
new file mode 100644
index 0000000..df1a51c
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java
@@ -0,0 +1,69 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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;
+
+import java.lang.invoke.MethodHandles;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * This class supports front-end web development. Placing this class in the test
+ * area allows excluding the mock configuration classes and the Mockito
+ * dependencies from the packaged version of the app.
+ *
+ * To launch a development server set the environment variable as listed below.
+ * This runs a Spring-Boot server with mock back-end beans, and keeps the server
+ * alive for manual testing. Supply this JVM argument:
+ *
+ * <pre>
+ * -Dorg.oransc.ric.portal.dashboard=mock
+ * </pre>
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
+@ActiveProfiles("test")
+public class DashboardTestServer {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /*
+ * Keeps the test server alive forever. Use a guard so this test is never run by
+ * Jenkins.
+ */
+ @EnabledIfSystemProperty(named = "org.oransc.ric.portal.dashboard", matches = "mock")
+ @Test
+ public void keepServerAlive() {
+ logger.warn("Keeping server alive!");
+ try {
+ synchronized (this) {
+ this.wait();
+ }
+ } catch (Exception ex) {
+ logger.warn(ex.toString());
+ }
+ }
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java
new file mode 100644
index 0000000..b056cb5
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java
@@ -0,0 +1,424 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.config;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.invoker.ApiClient;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPISchema;
+import org.oransc.ric.a1controller.client.model.OutputPISchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchemaOutput;
+import org.oransc.ric.portal.dashboard.model.PolicyType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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;
+import org.springframework.http.HttpStatus;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+/**
+ * Creates a mock implementation of the A1 controller client API.
+ */
+@Profile("test")
+@Configuration
+public class A1ControllerMockConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // A "control" is an element in the XApp descriptor
+ public static final String AC_CONTROL_NAME = "admission_control_policy";
+
+ // Simulate remote method delay for UI testing
+ @Value("${mock.config.delay:0}")
+ private int delayMs;
+
+ public A1ControllerMockConfiguration() {
+ logger.info("Configuring mock A1 Mediator");
+ }
+
+ private ApiClient apiClient() {
+ ApiClient mockClient = mock(ApiClient.class);
+ when(mockClient.getStatusCode()).thenReturn(HttpStatus.OK);
+ return mockClient;
+ }
+
+ @Bean
+ // Use the same name as regular configuration
+ public A1ControllerApi a1ControllerApi() {
+ ApiClient apiClient = apiClient();
+ A1ControllerApi mockApi = mock(A1ControllerApi.class);
+
+ when(mockApi.getApiClient()).thenReturn(apiClient);
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ List<Integer> types = database.getTypes();
+ OutputPTidsListSchemaOutput output = new OutputPTidsListSchemaOutput();
+ output.setPolicyTypeIdList(types);
+ OutputPTidsListSchema outputSchema = new OutputPTidsListSchema();
+ outputSchema.setOutput(output);
+ return outputSchema;
+ }).when(mockApi).a1ControllerGetAllPolicyTypes(any(InputNRRidSchema.class));
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetPolicyType sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ InputNRRidPTidSchema input = inv.<InputNRRidPTidSchema>getArgument(0);
+ PolicyType policyType = database.getPolicyType(input.getInput().getPolicyTypeId());
+ OutputDescNamePTSchemaOutput type = new OutputDescNamePTSchemaOutput();
+ type.setName(policyType.getName());
+ type.setDescription(policyType.getDescription());
+ type.setPolicyType(database.normalize(policyType.getCreateSchema()));
+ OutputDescNamePTSchema outputSchema = new OutputDescNamePTSchema();
+ outputSchema.setOutput(type);
+ return outputSchema;
+ }).when(mockApi).a1ControllerGetPolicyType(any(InputNRRidPTidSchema.class));
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ InputNRRidPTidSchema input = inv.<InputNRRidPTidSchema>getArgument(0);
+ List<String> instances = database.getInstances(Optional.of(input.getInput().getPolicyTypeId()));
+ OutputPIidsListSchemaOutput instancesOutput = new OutputPIidsListSchemaOutput();
+ instancesOutput.setPolicyInstanceIdList(instances);
+ OutputPIidsListSchema outputSchema = new OutputPIidsListSchema();
+ outputSchema.setOutput(instancesOutput);
+ return outputSchema;
+ }).when(mockApi).a1ControllerGetAllInstancesForType(any(InputNRRidPTidSchema.class));
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ InputNRRidPTidPIidSchema input = inv.<InputNRRidPTidPIidSchema>getArgument(0);
+ Integer polcyTypeId = input.getInput().getPolicyTypeId();
+ String instanceId = input.getInput().getPolicyInstanceId();
+ String instance = database.normalize(database.getInstance(polcyTypeId, instanceId));
+ OutputPISchemaOutput instanceOutput = new OutputPISchemaOutput();
+ instanceOutput.setPolicyInstance(instance);
+ OutputPISchema outputSchema = new OutputPISchema();
+ outputSchema.setOutput(instanceOutput);
+ return outputSchema;
+ }).when(mockApi).a1ControllerGetPolicyInstance(any(InputNRRidPTidPIidSchema.class));
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ InputNRRidPTidPIidPISchema input = inv.<InputNRRidPTidPIidPISchema>getArgument(0);
+ Integer polcyTypeId = input.getInput().getPolicyTypeId();
+ String instanceId = input.getInput().getPolicyInstanceId();
+ String instance = input.getInput().getPolicyInstance();
+ database.putInstance(polcyTypeId, instanceId, instance);
+ return null;
+ }).when(mockApi).a1ControllerCreatePolicyInstance(any(InputNRRidPTidPIidPISchema.class));
+
+ doAnswer(inv -> {
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
+ InputNRRidPTidPIidSchema input = inv.<InputNRRidPTidPIidSchema>getArgument(0);
+ Integer polcyTypeId = input.getInput().getPolicyTypeId();
+ String instanceId = input.getInput().getPolicyInstanceId();
+ database.deleteInstance(polcyTypeId, instanceId);
+ return null;
+ }).when(mockApi).a1ControllerDeletePolicyInstance(any(InputNRRidPTidPIidSchema.class));
+
+ return mockApi;
+ }
+
+ class Database {
+
+ private String schema1 = "{\"$schema\": " //
+ + "\"http://json-schema.org/draft-07/schema#\"," //
+ + "\"title\": \"ANR\"," //
+ + "\"description\": \"ANR Neighbour Cell Relation Policy\"," //
+ + "\"type\": \"object\"," //
+ + "\"properties\": " //
+ + "{ \"servingCellNrcgi\": {" //
+ + "\"type\": \"string\"," //
+ + "\"description\" : \"Serving Cell Identifier (NR CGI)\"}," //
+ + "\"neighborCellNrpci\": {" //
+ + "\"type\": \"string\"," //
+ + "\"description\": \"Neighbor Cell Identifier (NR PCI)\"}," //
+ + "\"neighborCellNrcgi\": {" //
+ + "\"type\": \"string\"," //
+ + "\"description\": \"Neighbor Cell Identifier (NR CGI)\"}," //
+ + "\"flagNoHo\": {" //
+ + "\"type\": \"boolean\"," //
+ + "\"description\": \"Flag for HANDOVER NOT ALLOWED\"}," //
+ + "\"flagNoXn\": {" //
+ + "\"type\": \"boolean\"," //
+ + "\"description\": \"Flag for Xn CONNECTION NOT ALLOWED\"}," //
+ + "\"flagNoRemove\": {" //
+ + "\"type\": \"boolean\"," //
+ + "\"description\": \"Flag for DELETION NOT ALLOWED\"}}, " //
+ + "\"required\": [ \"servingCellNrcgi\",\"neighborCellNrpci\",\"neighborCellNrcgi\",\"flagNoHo\",\"flagNoXn\",\"flagNoRemove\" ]}";
+ private PolicyType policy1 = new PolicyType(1, "ANR", "ANR Neighbour Cell Relation Policy", schema1);
+
+ private String policyInstance1 = "{\"servingCellNrcgi\": \"Cell1\",\r\n" + //
+ "\"neighborCellNrpci\": \"NCell1\",\r\n" + //
+ "\"neighborCellNrcgi\": \"Ncell1\",\r\n" + //
+ "\"flagNoHo\": true,\r\n" + //
+ "\"flagNoXn\": true,\r\n" + //
+ "\"flagNoRemove\": true}";
+
+ private String schema2 = "{\n" + " \"type\": \"object\",\n" + //
+ " \"title\": \"Car\",\n" + //
+ " \"properties\": {\n" + //
+ " \"make\": {\n" + //
+ " \"type\": \"string\",\n" + //
+ " \"enum\": [\n" + //
+ " \"Toyota\",\n" + //
+ " \"BMW\",\n" + //
+ " \"Honda\",\n" + //
+ " \"Ford\",\n" + //
+ " \"Chevy\",\n" + //
+ " \"VW\"\n" + //
+ " ]\n" + //
+ " },\n" + //
+ " \"model\": {\n" + //
+ " \"type\": \"string\"\n" + //
+ " },\n" + //
+ " \"year\": {\n" + //
+ " \"type\": \"integer\",\n" + //
+ " \"enum\": [\n" + //
+ " 1995,1996,1997,1998,1999,\n" + //
+ " 2000,2001,2002,2003,2004,\n" + //
+ " 2005,2006,2007,2008,2009,\n" + //
+ " 2010,2011,2012,2013,2014\n" + //
+ " ],\n" + //
+ " \"default\": 2008\n" + //
+ " },\n" + //
+ " \"safety\": {\n" + //
+ " \"type\": \"integer\",\n" + //
+ " \"format\": \"rating\",\n" + //
+ " \"maximum\": 5,\n" + //
+ " \"exclusiveMaximum\": false,\n" + //
+ " \"readonly\": false\n" + //
+ " }\n" + //
+ " }\n" + //
+ " }\n";
+ private PolicyType policy2 = new PolicyType(2, "type2", "Type2 description", schema2);
+
+ private String schema3 = "{\n" + //
+ " \"$id\": \"https://example.com/person.schema.json\",\n" + //
+ " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" + //
+ " \"title\": \"Person\",\n" + //
+ " \"type\": \"object\",\n" + //
+ " \"properties\": {\n" + //
+ " \"firstName\": {\n" + //
+ " \"type\": \"string\",\n" + //
+ " \"description\": \"The person's first name.\"\n" + //
+ " },\n" + //
+ " \"lastName\": {\n" + //
+ " \"type\": \"string\",\n" + //
+ " \"description\": \"The person's last name.\"\n" + //
+ " },\n" + //
+ " \"age\": {\n" + //
+ " \"description\": \"Age in years which must be equal to or greater than zero.\",\n" + //
+ " \"type\": \"integer\",\n" + //
+ " \"minimum\": 0\n" + //
+ " }\n" + //
+ " }\n" + //
+ "}";
+ private PolicyType policy3 = new PolicyType(3, "type3", "Type3 description", schema3);
+
+ private String schema4 = "{" + //
+ " \"$id\": \"https://example.com/arrays.schema.json\"," + //
+ " \"$schema\": \"http://json-schema.org/draft-07/schema#\"," + //
+ " \"description\": \"A representation of a person, company, organization, or place\"," + //
+ " \"type\": \"object\"," + //
+ " \"properties\": {" + //
+ " \"fruits\": {" + //
+ " \"type\": \"array\"," + //
+ " \"items\": {" + //
+ " \"type\": \"string\"" + //
+ " }" + //
+ " }," + //
+ " \"vegetables\": {" + //
+ " \"type\": \"array\"," + //
+ " \"items\": { \"$ref\": \"#/definitions/veggie\" }" + //
+ " }" + //
+ " }," + //
+ " \"definitions\": {" + //
+ " \"veggie\": {" + //
+ " \"type\": \"object\"," + //
+ " \"required\": [ \"veggieName\", \"veggieLike\" ]," + //
+ " \"properties\": {" + //
+ " \"veggieName\": {" + //
+ " \"type\": \"string\"," + //
+ " \"description\": \"The name of the vegetable.\"" + //
+ " }," + //
+ " \"veggieLike\": {" + //
+ " \"type\": \"boolean\"," + //
+ " \"description\": \"Do I like this vegetable?\"" + //
+ " }" + //
+ " }" + //
+ " }" + //
+ " }" + //
+ " }";
+ private PolicyType policy4 = new PolicyType(4, "type4", "Type4 description", schema4);
+
+ public class PolicyException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public PolicyException(String message) {
+ super(message);
+ System.out.println("**** Exception " + message);
+ }
+ }
+
+ private class PolicyTypeHolder {
+ PolicyTypeHolder(PolicyType pt) {
+ this.policyType = pt;
+ }
+
+ String getInstance(String instanceId) throws PolicyException {
+ String instance = instances.get(instanceId);
+ if (instance == null) {
+ throw new PolicyException("Instance not found: " + instanceId);
+ }
+ return instance;
+ }
+
+ PolicyType getPolicyType() {
+ return policyType;
+ }
+
+ void putInstance(String id, String data) {
+ instances.put(id, data);
+ }
+
+ void deleteInstance(String id) {
+ instances.remove(id);
+ }
+
+ List<String> getInstances() {
+ return new ArrayList<>(instances.keySet());
+ }
+
+ private final PolicyType policyType;
+ private Map<String, String> instances = new HashMap<>();
+ }
+
+ Database() {
+ types.put(1, new PolicyTypeHolder(policy1));
+ types.put(2, new PolicyTypeHolder(policy2));
+ types.put(3, new PolicyTypeHolder(policy3));
+ types.put(4, new PolicyTypeHolder(policy4));
+ try {
+ putInstance(1, "ANR-1", policyInstance1);
+ } catch (JsonProcessingException | PolicyException e) {
+ // Nothing
+ }
+ }
+
+ String normalize(String str) {
+ return str.replace('\n', ' ');
+ }
+
+ void putInstance(Integer typeId, String instanceId, String instanceData)
+ throws JsonProcessingException, PolicyException {
+ PolicyTypeHolder type = getTypeHolder(typeId);
+ type.putInstance(instanceId, instanceData);
+ }
+
+ void deleteInstance(Integer typeId, String instanceId) throws JsonProcessingException, PolicyException {
+ PolicyTypeHolder type = getTypeHolder(typeId);
+ type.deleteInstance(instanceId);
+ }
+
+ String getInstance(Integer typeId, String instanceId) throws JsonProcessingException, PolicyException {
+ return getTypeHolder(typeId).getInstance(instanceId);
+ }
+
+ List<Integer> getTypes() {
+ return new ArrayList<>(types.keySet());
+ }
+
+ List<String> getInstances(Optional<Integer> typeId) throws PolicyException {
+ if (typeId.isPresent()) {
+ return getTypeHolder(typeId.get()).getInstances();
+ } else {
+ Set<String> res = new HashSet<String>();
+ for (Iterator<PolicyTypeHolder> i = types.values().iterator(); i.hasNext();) {
+ res.addAll(i.next().getInstances());
+ }
+ return new ArrayList<>(res);
+ }
+ }
+
+ private PolicyTypeHolder getTypeHolder(Integer typeId) throws PolicyException {
+ PolicyTypeHolder typeHolder = types.get(typeId);
+ if (typeHolder == null) {
+ throw new PolicyException("Type not found: " + typeId);
+ }
+ return typeHolder;
+ }
+
+ private PolicyType getPolicyType(Integer typeId) throws PolicyException {
+ PolicyTypeHolder typeHolder = getTypeHolder(typeId);
+ return typeHolder.getPolicyType();
+ }
+
+ private Map<Integer, PolicyTypeHolder> types = new HashMap<>();
+
+ }
+
+ private final Database database = new Database();
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java
new file mode 100644
index 0000000..a3d05be
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java
@@ -0,0 +1,83 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@Profile("test")
+public class PortalApIMockConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Unfortunately EPSDK-FW does not define these as constants
+ public static final String PORTAL_USERNAME_HEADER_KEY = "username";
+ public static final String PORTAL_PASSWORD_HEADER_KEY = "password";
+
+ @Bean
+ public ServletRegistrationBean<PortalRestAPIProxy> portalApiProxyServlet() {
+ PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+ final ServletRegistrationBean<PortalRestAPIProxy> servletBean = new ServletRegistrationBean<>(servlet,
+ PortalApiConstants.API_PREFIX + "/*");
+ servletBean.setName("PortalRestApiProxyServlet");
+ return servletBean;
+ }
+
+ @Bean
+ public PortalAuthManager portalAuthManager() throws Exception {
+ PortalAuthManager mockManager = mock(PortalAuthManager.class);
+ final Map<String, String> credentialsMap = new HashMap<>();
+ credentialsMap.put("appName", "appName");
+ credentialsMap.put(PORTAL_USERNAME_HEADER_KEY, PORTAL_USERNAME_HEADER_KEY);
+ credentialsMap.put(PORTAL_PASSWORD_HEADER_KEY, PORTAL_PASSWORD_HEADER_KEY);
+ doAnswer(inv -> {
+ logger.debug("getAppCredentials");
+ return credentialsMap;
+ }).when(mockManager).getAppCredentials();
+ doAnswer(inv -> {
+ logger.debug("getUserId");
+ return "userId";
+ }).when(mockManager).validateEcompSso(any(HttpServletRequest.class));
+ doAnswer(inv -> {
+ logger.debug("getAppCredentials");
+ return credentialsMap;
+ }).when(mockManager).getAppCredentials();
+ return mockManager;
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
new file mode 100644
index 0000000..80cde66
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
@@ -0,0 +1,84 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.config;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("test")
+public class WebSecurityMockConfiguration extends WebSecurityConfigurerAdapter {
+
+ public static final String TEST_CRED_ADMIN = "admin";
+ public static final String TEST_CRED_STANDARD = "standard";
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public WebSecurityMockConfiguration(@Value("${userfile}") final String userFilePath) {
+ logger.debug("ctor: user file path {}", userFilePath);
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ auth.inMemoryAuthentication() //
+ .passwordEncoder(encoder) //
+ // The admin user has the admin AND standard roles
+ .withUser(TEST_CRED_ADMIN) //
+ .password(encoder.encode(TEST_CRED_ADMIN))
+ .roles(DashboardConstants.ROLE_NAME_ADMIN, DashboardConstants.ROLE_NAME_STANDARD)//
+ .and()//
+ // The standard user has only the standard role
+ .withUser(TEST_CRED_STANDARD) //
+ .password(encoder.encode(TEST_CRED_STANDARD)) //
+ .roles(DashboardConstants.ROLE_NAME_STANDARD);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.authorizeRequests().anyRequest().authenticated()//
+ .and().httpBasic() //
+ .and().csrf().disable();
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // This disables Spring security, but not the app's filter.
+ web.ignoring().antMatchers(WebSecurityConfiguration.OPEN_PATHS);
+ web.ignoring().antMatchers("/", "/csrf"); // allow swagger-ui to load
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
new file mode 100644
index 0000000..d4163f0
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
@@ -0,0 +1,112 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.Map;
+
+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.config.WebSecurityMockConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+// Need the fake answers from the backend
+@ActiveProfiles("test")
+public class AbstractControllerTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Created by Spring black magic
+ // https://spring.io/guides/gs/testing-web/
+ @LocalServerPort
+ private int localServerPort;
+
+ @Autowired
+ protected TestRestTemplate restTemplate;
+
+ /**
+ * Flexible URI builder.
+ *
+ * @param queryParams
+ * Map of string-string query parameters
+ * @param path
+ * Array of path components. If a component has an
+ * embedded slash, the string is split and each
+ * subcomponent is added individually.
+ * @return URI
+ */
+ protected URI buildUri(final Map<String, String> queryParams, final String... path) {
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:" + localServerPort + "/");
+ for (int p = 0; p < path.length; ++p) {
+ if (path[p] == null || path[p].isEmpty()) {
+ throw new IllegalArgumentException("Unexpected null or empty at path index " + Integer.toString(p));
+ } else if (path[p].contains("/")) {
+ String[] subpaths = path[p].split("/");
+ for (String s : subpaths)
+ if (!s.isEmpty())
+ builder.pathSegment(s);
+ } else {
+ builder.pathSegment(path[p]);
+ }
+ }
+ if (queryParams != null && queryParams.size() > 0) {
+ for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+ if (entry.getKey() == null || entry.getValue() == null)
+ throw new IllegalArgumentException("Unexpected null key or value");
+ else
+ builder.queryParam(entry.getKey(), entry.getValue());
+ }
+ }
+ return builder.build().encode().toUri();
+ }
+
+ // Because I put the annotations on this parent class,
+ // must define at least one test here.
+ @Test
+ public void contextLoads() {
+ // Silence Sonar warning about missing assertion.
+ Assertions.assertTrue(logger.isWarnEnabled());
+ logger.info("Context loads on mock profile");
+ }
+
+ public TestRestTemplate testRestTemplateAdminRole() {
+ return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_ADMIN,
+ WebSecurityMockConfiguration.TEST_CRED_ADMIN);
+ }
+
+ public TestRestTemplate testRestTemplateStandardRole() {
+ return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_STANDARD,
+ WebSecurityMockConfiguration.TEST_CRED_STANDARD);
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
new file mode 100644
index 0000000..48c5931
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
@@ -0,0 +1,48 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Tests whether the default (not mock) configuration classes run to completion.
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class DefaultContextTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Test
+ public void contextLoads() {
+ // Silence Sonar warning about missing assertion.
+ Assertions.assertTrue(logger.isWarnEnabled());
+ logger.info("Context loads on default profile");
+ }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
new file mode 100644
index 0000000..dc80968
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
@@ -0,0 +1,117 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.config.PortalApIMockConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+
+public class PortalRestCentralServiceTest extends AbstractControllerTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Test
+ public void getAnalyticsTest() {
+ // paths are hardcoded here exactly like the EPSDK-FW library :(
+ URI uri = buildUri(null, PortalApiConstants.API_PREFIX, "/analytics");
+ logger.info("Invoking {}", uri);
+ ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+ // No Portal is available so this always fails
+ Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+ }
+
+ @Test
+ public void getErrorPageTest() {
+ // Send unauthorized request
+ URI uri = buildUri(null, "/favicon.ico");
+ logger.info("Invoking {}", uri);
+ ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+ Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+ Assertions.assertTrue(response.getBody().contains("Static error page"));
+ }
+
+ private HttpEntity<Object> getEntityWithHeaders(Object body) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(PortalApIMockConfiguration.PORTAL_USERNAME_HEADER_KEY,
+ PortalApIMockConfiguration.PORTAL_USERNAME_HEADER_KEY);
+ headers.set(PortalApIMockConfiguration.PORTAL_PASSWORD_HEADER_KEY,
+ PortalApIMockConfiguration.PORTAL_PASSWORD_HEADER_KEY);
+ HttpEntity<Object> entity = new HttpEntity<>(body, headers);
+ return entity;
+ }
+
+ private EcompUser createEcompUser(String loginId) {
+ EcompUser user = new EcompUser();
+ user.setLoginId(loginId);
+ EcompRole role = new EcompRole();
+ role.setRoleFunctions(Collections.EMPTY_SET);
+ role.setId(1L);
+ role.setName(DashboardConstants.ROLE_NAME_ADMIN);
+ Set<EcompRole> roles = new HashSet<>();
+ roles.add(role);
+ user.setRoles(roles);
+ return user;
+ }
+
+/* @Test
+ public void createUserTest() {
+ final String loginId = "login1";
+ URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+ logger.info("Invoking {}", create);
+ HttpEntity<Object> requestEntity = getEntityWithHeaders(createEcompUser(loginId));
+ ResponseEntity<String> response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ }
+
+ @Test
+ public void updateUserTest() {
+ final String loginId = "login2";
+ URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+ EcompUser user = createEcompUser(loginId);
+ logger.info("Invoking {}", create);
+ HttpEntity<Object> requestEntity = getEntityWithHeaders(user);
+ // Create
+ ResponseEntity<String> response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ URI update = buildUri(null, PortalApiConstants.API_PREFIX, "user", loginId);
+ user.setEmail("user@company.org");
+ requestEntity = getEntityWithHeaders(user);
+ response = restTemplate.exchange(update, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ }
+*/
+
+}
diff --git a/dashboard/webapp-backend/src/test/resources/anr-policy-instance.json b/dashboard/webapp-backend/src/test/resources/anr-policy-instance.json
new file mode 100644
index 0000000..0d7315e
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/anr-policy-instance.json
@@ -0,0 +1,8 @@
+{
+ "servingCellNrcgi": "Cell1",
+ "neighborCellNrpci": "NCell1",
+ "neighborCellNrcgi": "Ncell1",
+ "flagNoHo": true,
+ "flagNoXn": true,
+ "flagNoRemove": true
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/anr-policy-schema.json b/dashboard/webapp-backend/src/test/resources/anr-policy-schema.json
new file mode 100644
index 0000000..6e0263d
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/anr-policy-schema.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "ANR",
+ "description": "ANR Neighbour Cell Relation Policy",
+ "type": "object",
+ "properties": {
+ "servingCellNrcgi": {
+ "type": "string",
+ "description": "Serving Cell Identifier (NR CGI)"
+ },
+ "neighborCellNrpci": {
+ "type": "string",
+ "description": "Neighbor Cell Identifier (NR PCI)"
+ },
+ "neighborCellNrcgi": {
+ "type": "string",
+ "description": "Neighbor Cell Identifier (NR CGI)"
+ },
+ "flagNoHo": {
+ "type": "boolean",
+ "description": "Flag for HANDOVER NOT ALLOWED"
+ },
+ "flagNoXn": {
+ "type": "boolean",
+ "description": "Flag for Xn CONNECTION NOT ALLOWED"
+ },
+ "flagNoRemove": {
+ "type": "boolean",
+ "description": "Flag for DELETION NOT ALLOWED"
+ }
+ },
+ "required": [
+ "servingCellNrcgi",
+ "neighborCellNrpci",
+ "neighborCellNrcgi",
+ "flagNoHo",
+ "flagNoXn",
+ "flagNoRemove"
+ ]
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json
new file mode 100644
index 0000000..216b7db
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json
@@ -0,0 +1,9 @@
+{
+ "kind": "PodList",
+ "apiVersion": "v1",
+ "metadata": {
+ "selfLink": "/api/v1/namespaces/ricaux/pods",
+ "resourceVersion": "1594484"
+ },
+ "items": []
+}
diff --git a/dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json
new file mode 100644
index 0000000..cf9c1a0
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json
@@ -0,0 +1,1847 @@
+{
+ "kind": "PodList",
+ "apiVersion": "v1",
+ "metadata": {
+ "selfLink": "/api/v1/namespaces/ricplt/pods",
+ "resourceVersion": "3189380"
+ },
+ "items": [
+ {
+ "metadata": {
+ "name": "deployment-ricplt-a1mediator-74d9fc4c8c-77vtp",
+ "generateName": "deployment-ricplt-a1mediator-74d9fc4c8c-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-a1mediator-74d9fc4c8c-77vtp",
+ "uid": "487c582d-36d6-406a-92a3-bfbce04b83de",
+ "resourceVersion": "2945631",
+ "creationTimestamp": "2019-10-18T16:15:04Z",
+ "labels": {
+ "app": "ricplt-a1mediator",
+ "group": "nagios",
+ "pod-template-hash": "74d9fc4c8c",
+ "release": "r1-a1mediator"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-a1mediator-74d9fc4c8c",
+ "uid": "84ef8695-3eb2-4dcd-b214-9d00ab5fb6b3",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "a1conf",
+ "configMap": {
+ "name": "configmap-ricplt-a1mediator-a1conf",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-a1mediator",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1:0.10.3",
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 10000,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 4562,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-a1mediator-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "a1conf",
+ "mountPath": "/opt/ricmanifest.json",
+ "subPath": "ricmanifest.json"
+ },
+ {
+ "name": "a1conf",
+ "mountPath": "/opt/rmr_string_int_mapping.txt",
+ "subPath": "rmr_string_int_mapping.txt"
+ },
+ {
+ "name": "a1conf",
+ "mountPath": "/opt/route/local.rt",
+ "subPath": "local.rt"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "livenessProbe": {
+ "httpGet": {
+ "path": "/a1-p/healthcheck",
+ "port": "http",
+ "scheme": "HTTP"
+ },
+ "timeoutSeconds": 1,
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "failureThreshold": 3
+ },
+ "readinessProbe": {
+ "httpGet": {
+ "path": "/a1-p/healthcheck",
+ "port": "http",
+ "scheme": "HTTP"
+ },
+ "timeoutSeconds": 1,
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "failureThreshold": 3
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.202",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "a1mediator",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:04Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:09Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:09Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:04Z"
+ }
+ ],
+ "hostIP": "172.29.16.202",
+ "podIP": "10.244.4.71",
+ "podIPs": [
+ {
+ "ip": "10.244.4.71"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:04Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-a1mediator",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:05Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1:0.10.3",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1@sha256:64a61ed84d4d05dfa1690bb45949da333d7b088e2e12dbba0ce60c06445f52cb",
+ "containerID": "docker://07ceed10ee2a4413c167951e458b59a505a073cce82ac543146e58b698489d59",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-appmgr-6ccf55b98b-kbkt4",
+ "generateName": "deployment-ricplt-appmgr-6ccf55b98b-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-appmgr-6ccf55b98b-kbkt4",
+ "uid": "4e084e8a-eb0a-4ea2-9cc1-7f812cd6bb28",
+ "resourceVersion": "2945633",
+ "creationTimestamp": "2019-10-18T16:15:01Z",
+ "labels": {
+ "app": "ricplt-appmgr",
+ "group": "nagios",
+ "pod-template-hash": "6ccf55b98b",
+ "release": "r1-appmgr"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-appmgr-6ccf55b98b",
+ "uid": "a169ebc4-9b7c-4b8d-81ed-6364e07df24e",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "config-volume",
+ "configMap": {
+ "name": "configmap-ricplt-appmgr-appconfig",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "cert-volume",
+ "configMap": {
+ "name": "xapp-mgr-certs",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "secret-volume",
+ "secret": {
+ "secretName": "xapp-mgr-creds",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "helm-secret-volume",
+ "emptyDir": {
+
+ }
+ },
+ {
+ "name": "appmgr-bin-volume",
+ "configMap": {
+ "name": "configmap-ricplt-appmgr-bin",
+ "defaultMode": 493
+ }
+ },
+ {
+ "name": "svcacct-ricplt-appmgr-token-kclzw",
+ "secret": {
+ "secretName": "svcacct-ricplt-appmgr-token-kclzw",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "initContainers": [
+ {
+ "name": "container-ricplt-appmgr-copy-tiller-secret",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/it-dep-init:0.0.1",
+ "command": [
+ "/appmgr-tiller-secret-copier.sh"
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-appmgr-env"
+ }
+ }
+ ],
+ "env": [
+ {
+ "name": "SVCACCT_NAME",
+ "value": "svcacct-ricplt-appmgr"
+ },
+ {
+ "name": "CLUSTER_NAME",
+ "value": "kubernetes"
+ },
+ {
+ "name": "KUBECONFIG",
+ "value": "/tmp/kubeconfig"
+ },
+ {
+ "name": "K8S_API_HOST",
+ "value": "https://10.254.0.1:443"
+ },
+ {
+ "name": "SECRET_NAMESPACE",
+ "value": "ricinfra"
+ },
+ {
+ "name": "SECRET_NAME",
+ "value": "secret-helm-client-ricxapp"
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "helm-secret-volume",
+ "mountPath": "/opt/ric/secret"
+ },
+ {
+ "name": "appmgr-bin-volume",
+ "mountPath": "/svcacct-to-kubeconfig.sh",
+ "subPath": "svcacct-to-kubeconfig.sh"
+ },
+ {
+ "name": "appmgr-bin-volume",
+ "mountPath": "/appmgr-tiller-secret-copier.sh",
+ "subPath": "appmgr-tiller-secret-copier.sh"
+ },
+ {
+ "name": "svcacct-ricplt-appmgr-token-kclzw",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "IfNotPresent"
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-appmgr",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr:0.1.9",
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 8080,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 4560,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-appmgr-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "config-volume",
+ "mountPath": "/opt/ric/config/appmgr.yaml",
+ "subPath": "appmgr.yaml"
+ },
+ {
+ "name": "cert-volume",
+ "mountPath": "/opt/ric/certificates"
+ },
+ {
+ "name": "helm-secret-volume",
+ "mountPath": "/opt/ric/secret"
+ },
+ {
+ "name": "secret-volume",
+ "mountPath": "/opt/ric/secret/helm_repo_username",
+ "subPath": "helm_repo_username"
+ },
+ {
+ "name": "secret-volume",
+ "mountPath": "/opt/ric/secret/helm_repo_password",
+ "subPath": "helm_repo_password"
+ },
+ {
+ "name": "svcacct-ricplt-appmgr-token-kclzw",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "svcacct-ricplt-appmgr",
+ "serviceAccount": "svcacct-ricplt-appmgr",
+ "nodeName": "172.29.16.203",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "appmgr",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:03Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:04Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:04Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:01Z"
+ }
+ ],
+ "hostIP": "172.29.16.203",
+ "podIP": "10.244.3.252",
+ "podIPs": [
+ {
+ "ip": "10.244.3.252"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:01Z",
+ "initContainerStatuses": [
+ {
+ "name": "container-ricplt-appmgr-copy-tiller-secret",
+ "state": {
+ "terminated": {
+ "exitCode": 0,
+ "reason": "Completed",
+ "startedAt": "2019-10-18T16:15:02Z",
+ "finishedAt": "2019-10-18T16:15:02Z",
+ "containerID": "docker://130db0adfad526204726bf11fe24741d94f11f39f97f0d826b066ec852e5a452"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/it-dep-init:0.0.1",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/it-dep-init@sha256:d3a2c02660a0b5da5a7e38626c49018ca7f5e3bc39106b0728ff72245cd20be5",
+ "containerID": "docker://130db0adfad526204726bf11fe24741d94f11f39f97f0d826b066ec852e5a452"
+ }
+ ],
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-appmgr",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:03Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr:0.1.9",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr@sha256:5c076f702d570b385d10200cda8d504475ce44eb1bcbb131b1d50e00eabae4d7",
+ "containerID": "docker://791f455c1974a100aaa09ab0a290e438d75aa1c3aadcb717c42d53e02cdedb83",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-dbaas-d4c9f7b88-7wgb9",
+ "generateName": "deployment-ricplt-dbaas-d4c9f7b88-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-dbaas-d4c9f7b88-7wgb9",
+ "uid": "e54e51fc-c4bd-4308-805e-16c2d588dacd",
+ "resourceVersion": "2945634",
+ "creationTimestamp": "2019-10-18T16:15:01Z",
+ "labels": {
+ "app": "ricplt-dbaas",
+ "group": "nagios",
+ "pod-template-hash": "d4c9f7b88",
+ "release": "r1-dbaas"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-dbaas-d4c9f7b88",
+ "uid": "7e8d5d34-efa9-41fe-b92f-d9b71bc40360",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-dbaas",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas:0.1.0",
+ "ports": [
+ {
+ "name": "sql",
+ "containerPort": 6379,
+ "protocol": "TCP"
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 0,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.202",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:01Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:03Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:03Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:01Z"
+ }
+ ],
+ "hostIP": "172.29.16.202",
+ "podIP": "10.244.4.70",
+ "podIPs": [
+ {
+ "ip": "10.244.4.70"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:01Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-dbaas",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:02Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas:0.1.0",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas@sha256:f63cfa353f355155ec6455a68d18c631900a2602bf7cc2ba35d6210971736b76",
+ "containerID": "docker://8972d8b61d5c3ff56b50814575647d70fb3307602506cda3e34b6734c28a3f36",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-e2mgr-86c76477c5-mf5t5",
+ "generateName": "deployment-ricplt-e2mgr-86c76477c5-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-e2mgr-86c76477c5-mf5t5",
+ "uid": "5f2231ea-bec0-46fc-a8f8-09b0b80e982f",
+ "resourceVersion": "2945636",
+ "creationTimestamp": "2019-10-18T16:15:02Z",
+ "labels": {
+ "app": "ricplt-e2mgr",
+ "group": "nagios",
+ "pod-template-hash": "86c76477c5",
+ "release": "r1-e2mgr"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-e2mgr-86c76477c5",
+ "uid": "f7dfd4a3-4eb3-4c46-a6c8-adc4ae37ef57",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "local-router-file",
+ "configMap": {
+ "name": "configmap-ricplt-e2mgr-router-configmap",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "local-configuration-file",
+ "configMap": {
+ "name": "configmap-ricplt-e2mgr-configuration-configmap",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-e2mgr",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr:2.0.7",
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 3800,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 3801,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-e2mgr-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "local-router-file",
+ "mountPath": "/opt/E2Manager/router.txt",
+ "subPath": "router.txt"
+ },
+ {
+ "name": "local-configuration-file",
+ "mountPath": "/opt/E2Manager/resources/configuration.yaml",
+ "subPath": "configuration.yaml"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always",
+ "securityContext": {
+ "privileged": false
+ },
+ "stdin": true,
+ "tty": true
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.204",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "e2mgr",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:02Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:05Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:05Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:02Z"
+ }
+ ],
+ "hostIP": "172.29.16.204",
+ "podIP": "10.244.2.100",
+ "podIPs": [
+ {
+ "ip": "10.244.2.100"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:02Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-e2mgr",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:04Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr:2.0.7",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr@sha256:87fa19a934215bdec71a355ef08eec9e273c992bab80af727f4f1b7a74ebacfa",
+ "containerID": "docker://ff3a2fcfc72b00e3c317899f2b620da2f65e3de260623daed7825f6a74dbcb5c",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-e2term-5dcf8b54b-5mkxl",
+ "generateName": "deployment-ricplt-e2term-5dcf8b54b-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-e2term-5dcf8b54b-5mkxl",
+ "uid": "8fae1dcd-5e42-4a66-be6f-e893d5563689",
+ "resourceVersion": "2945639",
+ "creationTimestamp": "2019-10-18T16:15:03Z",
+ "labels": {
+ "app": "ricplt-e2term",
+ "group": "nagios",
+ "pod-template-hash": "5dcf8b54b",
+ "release": "r1-e2term"
+ },
+ "annotations": {
+ "danm.k8s.io/interfaces": "[\n {\"clusterNetwork\":\"default\", \"ip\":\"dynamic\"},\n {\"clusterNetwork\":\"ran\", \"ip\":\"dynamic\", \"ip6\":\"dynamic\"}\n]\n",
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-e2term-5dcf8b54b",
+ "uid": "cc20b8a0-6d74-4fb9-b384-bdce9c9ae184",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "local-router-file",
+ "configMap": {
+ "name": "configmap-ricplt-e2term-router-configmap",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "localtime",
+ "hostPath": {
+ "path": "/etc/localtime",
+ "type": ""
+ }
+ },
+ {
+ "name": "pizpub-config",
+ "configMap": {
+ "name": "configmap-ricplt-e2term-pizpub",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "vol-shared",
+ "persistentVolumeClaim": {
+ "claimName": "pvc-ricplt-e2term"
+ }
+ },
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-e2term",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2:2.0.7.5",
+ "ports": [
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 38000,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-e2term-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "local-router-file",
+ "mountPath": "/opt/e2/router.txt",
+ "subPath": "router.txt"
+ },
+ {
+ "name": "local-router-file",
+ "mountPath": "/tmp/rmr_verbose",
+ "subPath": "rmr_verbose"
+ },
+ {
+ "name": "vol-shared",
+ "mountPath": "/data/outgoing/",
+ "subPath": "outgoing"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always",
+ "securityContext": {
+ "privileged": false
+ },
+ "stdin": true,
+ "tty": true
+ },
+ {
+ "name": "container-ricplt-e2term-pizpub",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/pizpub:0.0.5155",
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "localtime",
+ "readOnly": true,
+ "mountPath": "/etc/localtime"
+ },
+ {
+ "name": "vol-shared",
+ "mountPath": "/data"
+ },
+ {
+ "name": "pizpub-config",
+ "mountPath": "/opt/app/config/conf/"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "lifecycle": {
+ "postStart": {
+ "exec": {
+ "command": [
+ "/bin/sh",
+ "/opt/app/config/conf/cleaner.sh",
+ "/data/sent",
+ "3"
+ ]
+ }
+ }
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirstWithHostNet",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.201",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "e2term",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:22Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:33Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:33Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:22Z"
+ }
+ ],
+ "hostIP": "172.29.16.201",
+ "podIP": "10.244.1.90",
+ "podIPs": [
+ {
+ "ip": "10.244.1.90"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:22Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-e2term",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:31Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "ranco-dev-tools.eastus.cloudapp.azure.com:10001/ric-plt-e2:2.0.7.5",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2@sha256:0ea1a356d018495a93e124ddd793e09626bf6e4d9b96355e731673ef7fab5a1f",
+ "containerID": "docker://bf9ca87dbad9436b0ed99ffe38036fb49033a9bc2cf2eb548397fbc9c48f1c3d",
+ "started": true
+ },
+ {
+ "name": "container-ricplt-e2term-pizpub",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:32Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "ranco-dev-tools.eastus.cloudapp.azure.com:10001/pizpub:0.0.5155",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/pizpub@sha256:138c2d2d25e6528c4a5a8a402c277722d1c1fd4d6792b644967acd538affb1ed",
+ "containerID": "docker://93e39661623b7afc8156008bb6fbc206458964a6eb0633f80164e4c7ef59fe76",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-jaegeradapter-6ff8676c7-m4qkf",
+ "generateName": "deployment-ricplt-jaegeradapter-6ff8676c7-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-jaegeradapter-6ff8676c7-m4qkf",
+ "uid": "d6a5e9e9-87d0-4d1e-b1b7-cc1a4f20dc2e",
+ "resourceVersion": "2945640",
+ "creationTimestamp": "2019-10-18T16:15:08Z",
+ "labels": {
+ "app": "ricplt-jaegeradapter",
+ "group": "nagios",
+ "pod-template-hash": "6ff8676c7",
+ "release": "r1-jaegeradapter"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-jaegeradapter-6ff8676c7",
+ "uid": "98bc03d7-a082-4ac1-9b89-064022a37dff",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-jaegeradapter",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/all-in-one:1.12",
+ "ports": [
+ {
+ "name": "zipkincompact",
+ "containerPort": 5775,
+ "protocol": "UDP"
+ },
+ {
+ "name": "jaegercompact",
+ "containerPort": 6831,
+ "protocol": "UDP"
+ },
+ {
+ "name": "jaegerbinary",
+ "containerPort": 6832,
+ "protocol": "UDP"
+ },
+ {
+ "name": "httpquery",
+ "containerPort": 16686,
+ "protocol": "TCP"
+ },
+ {
+ "name": "httpconfig",
+ "containerPort": 5778,
+ "protocol": "TCP"
+ },
+ {
+ "name": "zipkinhttp",
+ "containerPort": 9411,
+ "protocol": "TCP"
+ },
+ {
+ "name": "jaegerhttp",
+ "containerPort": 14268,
+ "protocol": "TCP"
+ },
+ {
+ "name": "jaegerhttpt",
+ "containerPort": 14267,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-jaegeradapter"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "livenessProbe": {
+ "httpGet": {
+ "path": "/",
+ "port": 16686,
+ "scheme": "HTTP"
+ },
+ "timeoutSeconds": 1,
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "failureThreshold": 3
+ },
+ "readinessProbe": {
+ "httpGet": {
+ "path": "/",
+ "port": 16686,
+ "scheme": "HTTP"
+ },
+ "timeoutSeconds": 1,
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "failureThreshold": 3
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.203",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "jaegeradapter",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:08Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:15Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:15Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:08Z"
+ }
+ ],
+ "hostIP": "172.29.16.203",
+ "podIP": "10.244.3.254",
+ "podIPs": [
+ {
+ "ip": "10.244.3.254"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:08Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-jaegeradapter",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:09Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/all-in-one:1.12",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/all-in-one@sha256:51b901b653f4a4ca5dd50be9133c08dacf2d3eb092e507c213e7955a0e132297",
+ "containerID": "docker://95013a49a1705a503f5f7dde7a38fa7277523a73cdef96d264fcefe170e8a921",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-rsm-88477585f-qkkj7",
+ "generateName": "deployment-ricplt-rsm-88477585f-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-rsm-88477585f-qkkj7",
+ "uid": "d4c58ff4-743e-4ed6-bd36-aeb02daa1ca6",
+ "resourceVersion": "2945642",
+ "creationTimestamp": "2019-10-18T16:15:07Z",
+ "labels": {
+ "app": "ricplt-rsm",
+ "group": "nagios",
+ "pod-template-hash": "88477585f",
+ "release": "r1-rsm"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-rsm-88477585f",
+ "uid": "1fe7de57-90d9-4898-9b71-1ae9c4a6f014",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "local-router-file",
+ "configMap": {
+ "name": "configmap-ricplt-rsm-router-configmap",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "local-configuration-file",
+ "configMap": {
+ "name": "configmap-ricplt-rsm",
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-rsm",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm:2.0.7",
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 4800,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 4801,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-rsm-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "local-router-file",
+ "mountPath": "/opt/RSM/router.txt",
+ "subPath": "router.txt"
+ },
+ {
+ "name": "local-configuration-file",
+ "mountPath": "/opt/RSM/resources/configuration.yaml",
+ "subPath": "configuration.yaml"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always",
+ "securityContext": {
+ "privileged": false
+ },
+ "stdin": true,
+ "tty": true
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.203",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "rsm",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:07Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:09Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:09Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:07Z"
+ }
+ ],
+ "hostIP": "172.29.16.203",
+ "podIP": "10.244.3.253",
+ "podIPs": [
+ {
+ "ip": "10.244.3.253"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:07Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-rsm",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:08Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm:2.0.7",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm@sha256:e6fb3bc17fcd5a2fbc7d34eeb744fbfed4eaaaf6c669e084b379ee05368820d3",
+ "containerID": "docker://5e90673a6b2c292f2ce7c731bf8747c8a63f429eca08d08a993130001c7d6f5e",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-submgr-7549b87fb8-4t6mx",
+ "generateName": "deployment-ricplt-submgr-7549b87fb8-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-submgr-7549b87fb8-4t6mx",
+ "uid": "c6fbd48b-2757-421c-a534-f1931b04312b",
+ "resourceVersion": "2945646",
+ "creationTimestamp": "2019-10-18T16:15:05Z",
+ "labels": {
+ "app": "ricplt-submgr",
+ "group": "nagios",
+ "pod-template-hash": "7549b87fb8",
+ "release": "r1-submgr"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-submgr-7549b87fb8",
+ "uid": "e2b9dd9f-cca4-4f64-9e11-b6ee174c4f6f",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "config-volume",
+ "configMap": {
+ "name": "submgrcfg",
+ "items": [
+ {
+ "key": "submgrcfg",
+ "path": "submgr-config.yaml",
+ "mode": 420
+ }
+ ],
+ "defaultMode": 420
+ }
+ },
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-submgr",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr:0.10.5",
+ "command": [
+ "/run_submgr.sh"
+ ],
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 3800,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrroute",
+ "containerPort": 4561,
+ "protocol": "TCP"
+ },
+ {
+ "name": "rmrdata",
+ "containerPort": 4560,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-submgr-env"
+ }
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "config-volume",
+ "mountPath": "/cfg"
+ },
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.205",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "submgr",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:05Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:07Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:07Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:05Z"
+ }
+ ],
+ "hostIP": "172.29.16.205",
+ "podIP": "10.244.0.168",
+ "podIPs": [
+ {
+ "ip": "10.244.0.168"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:05Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-submgr",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:07Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr:0.10.5",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr@sha256:aa8ada253d0800a849b6124fc54793815caaf93ad46b8d47cdd1f590ef69f813",
+ "containerID": "docker://724ba7834ef80d1f3c85ae7990ead480ed5226f0275816bae358edc9ddf54da6",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ },
+ {
+ "metadata": {
+ "name": "deployment-ricplt-vespamgr-55f6484b7-g5zfw",
+ "generateName": "deployment-ricplt-vespamgr-55f6484b7-",
+ "namespace": "ricplt",
+ "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-vespamgr-55f6484b7-g5zfw",
+ "uid": "a7ae0f4a-adfa-48c6-8c41-725ba1c84b11",
+ "resourceVersion": "2945649",
+ "creationTimestamp": "2019-10-18T16:15:06Z",
+ "labels": {
+ "app": "ricplt-vespamgr",
+ "group": "nagios",
+ "pod-template-hash": "55f6484b7",
+ "release": "r1-vespamgr"
+ },
+ "annotations": {
+ "kubernetes.io/psp": "caas-default"
+ },
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "name": "deployment-ricplt-vespamgr-55f6484b7",
+ "uid": "dc5e0e81-23da-4fed-99da-14cb7a8fe06c",
+ "controller": true,
+ "blockOwnerDeletion": true
+ }
+ ]
+ },
+ "spec": {
+ "volumes": [
+ {
+ "name": "default-token-5j24g",
+ "secret": {
+ "secretName": "default-token-5j24g",
+ "defaultMode": 420
+ }
+ }
+ ],
+ "containers": [
+ {
+ "name": "container-ricplt-vespamgr",
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr:0.0.5",
+ "ports": [
+ {
+ "name": "http",
+ "containerPort": 8080,
+ "protocol": "TCP"
+ }
+ ],
+ "envFrom": [
+ {
+ "configMapRef": {
+ "name": "configmap-ricplt-vespamgr"
+ }
+ },
+ {
+ "secretRef": {
+ "name": "vespa-secrets"
+ }
+ }
+ ],
+ "env": [
+ {
+ "name": "VESMGR_APPMGRDOMAIN",
+ "value": "appmgr-service"
+ }
+ ],
+ "resources": {
+
+ },
+ "volumeMounts": [
+ {
+ "name": "default-token-5j24g",
+ "readOnly": true,
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+ }
+ ],
+ "livenessProbe": {
+ "httpGet": {
+ "path": "/supervision",
+ "port": 8080,
+ "scheme": "HTTP"
+ },
+ "initialDelaySeconds": 30,
+ "timeoutSeconds": 20,
+ "periodSeconds": 60,
+ "successThreshold": 1,
+ "failureThreshold": 3
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "imagePullPolicy": "Always"
+ }
+ ],
+ "restartPolicy": "Always",
+ "terminationGracePeriodSeconds": 30,
+ "dnsPolicy": "ClusterFirst",
+ "serviceAccountName": "default",
+ "serviceAccount": "default",
+ "nodeName": "172.29.16.204",
+ "securityContext": {
+
+ },
+ "imagePullSecrets": [
+ {
+ "name": "docker-reg-cred"
+ }
+ ],
+ "hostname": "vespamgr",
+ "schedulerName": "default-scheduler",
+ "enableServiceLinks": true
+ },
+ "status": {
+ "phase": "Running",
+ "conditions": [
+ {
+ "type": "Initialized",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:06Z"
+ },
+ {
+ "type": "Ready",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:08Z"
+ },
+ {
+ "type": "ContainersReady",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:08Z"
+ },
+ {
+ "type": "PodScheduled",
+ "status": "True",
+ "lastProbeTime": null,
+ "lastTransitionTime": "2019-10-18T16:15:06Z"
+ }
+ ],
+ "hostIP": "172.29.16.204",
+ "podIP": "10.244.2.101",
+ "podIPs": [
+ {
+ "ip": "10.244.2.101"
+ }
+ ],
+ "startTime": "2019-10-18T16:15:06Z",
+ "containerStatuses": [
+ {
+ "name": "container-ricplt-vespamgr",
+ "state": {
+ "running": {
+ "startedAt": "2019-10-18T16:15:08Z"
+ }
+ },
+ "lastState": {
+
+ },
+ "ready": true,
+ "restartCount": 0,
+ "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr:0.0.5",
+ "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr@sha256:97753ef72471a5fddd59ff35a2fe763b041848f6e83214acb78ad73c7316b371",
+ "containerID": "docker://80884c969cbf802945c075afc47d747b5e747e4645c691d376820c9d61094e7c",
+ "started": true
+ }
+ ],
+ "qosClass": "BestEffort"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json
new file mode 100644
index 0000000..09999ef
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json
@@ -0,0 +1,53 @@
+{
+ "type": "object",
+ "title": "Car",
+ "properties": {
+ "make": {
+ "type": "string",
+ "enum": [
+ "Toyota",
+ "BMW",
+ "Honda",
+ "Ford",
+ "Chevy",
+ "VW"
+ ]
+ },
+ "model": {
+ "type": "string"
+ },
+ "year": {
+ "type": "integer",
+ "enum": [
+ 1995,
+ 1996,
+ 1997,
+ 1998,
+ 1999,
+ 2000,
+ 2001,
+ 2002,
+ 2003,
+ 2004,
+ 2005,
+ 2006,
+ 2007,
+ 2008,
+ 2009,
+ 2010,
+ 2011,
+ 2012,
+ 2013,
+ 2014
+ ],
+ "default": 2008
+ },
+ "safety": {
+ "type": "integer",
+ "format": "rating",
+ "maximum": 5,
+ "exclusiveMaximum": false,
+ "readonly": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json
new file mode 100644
index 0000000..69ec678
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json
@@ -0,0 +1,21 @@
+{
+ "$id": "https://example.com/person.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Person",
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "description": "The person's first name."
+ },
+ "lastName": {
+ "type": "string",
+ "description": "The person's last name."
+ },
+ "age": {
+ "description": "Age in years which must be equal to or greater than zero.",
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json
new file mode 100644
index 0000000..3be9959
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json
@@ -0,0 +1,39 @@
+{
+ "$id": "https://example.com/arrays.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "A representation of a person, company, organization, or place",
+ "type": "object",
+ "properties": {
+ "fruits": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "vegetables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/veggie"
+ }
+ }
+ },
+ "definitions": {
+ "veggie": {
+ "type": "object",
+ "required": [
+ "veggieName",
+ "veggieLike"
+ ],
+ "properties": {
+ "veggieName": {
+ "type": "string",
+ "description": "The name of the vegetable."
+ },
+ "veggieLike": {
+ "type": "boolean",
+ "description": "Do I like this vegetable?"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/key.properties b/dashboard/webapp-backend/src/test/resources/key.properties
new file mode 100644
index 0000000..85a5689
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/key.properties
@@ -0,0 +1,22 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+cipher.enc.key = bogus
diff --git a/dashboard/webapp-backend/src/test/resources/portal.properties b/dashboard/webapp-backend/src/test/resources/portal.properties
new file mode 100644
index 0000000..326539e
--- /dev/null
+++ b/dashboard/webapp-backend/src/test/resources/portal.properties
@@ -0,0 +1,26 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 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===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+ecomp_redirect_url = https://www.wikipedia.org
+ecomp_rest_url = http://localhost/portal
+ueb_app_key = abcdef1234567890