[Solution] Update to ONAP Montreal releases version

- update controller/odlux images
- change persistence from elasticsearch to mariaDB
- improved user handling for identity
- support indentity theme
Issue-ID: OAM-403
Change-Id: I28bec223a656af1dcfe61b47f5df43fba4545a52
Signed-off-by: demskeq8 <alexander.dehn@highstreet-technologies.com>
diff --git a/solution/smo/common/.env b/solution/smo/common/.env
index cc337b4..392b892 100644
--- a/solution/smo/common/.env
+++ b/solution/smo/common/.env
@@ -38,7 +38,7 @@
 IDENTITY_PROVIDER_URL=https://identity.${HTTP_DOMAIN}
 
 # PERSISTENCE (including SDN-R Database)
-PERSISTENCE_IMAGE=docker.elastic.co/elasticsearch/elasticsearch-oss:7.9.3
+PERSISTENCE_IMAGE=mariadb:11.1.2
 
 ## ZooKeeper
 ZOOKEEPER_IMAGE=nexus3.onap.org:10001/onap/dmaap/zookeeper:6.0.3
diff --git a/solution/smo/common/docker-compose.yaml b/solution/smo/common/docker-compose.yaml
index 7afa49b..772d073 100755
--- a/solution/smo/common/docker-compose.yaml
+++ b/solution/smo/common/docker-compose.yaml
@@ -14,27 +14,29 @@
 # limitations under the License.
 #
 # no more versions needed! Compose spec supports all features w/o a version
+version: "3.8"
 services:
-
   gateway:
     image: ${TRAEFIK_IMAGE}
     container_name: gateway
     hostname: gateway
     healthcheck:
       test:
-        - CMD
-        - traefik
-        - healthcheck
-        - --ping
+        [
+          "CMD",
+          "traefik",
+          "healthcheck",
+          "--ping"
+        ]
       interval: 10s
       timeout: 5s
       retries: 3
     restart: always
     ports:
-      - 80:80
-      - 443:443
-      - 4334:4334
-      - 4335:4335
+      - "80:80"
+      - "443:443"
+      - "4334:4334"
+      - "4335:4335"
     command:
       - --serverstransport.insecureskipverify=true
       - --log.level=${TRAEFIK_LOG_LEVEL}
@@ -72,6 +74,9 @@
       traefik.http.middlewares.strip.stripprefix.prefixes: /traefik
       traefik.http.routers.gateway.tls: true
       traefik.http.services.gateway.loadbalancer.server.port: 8080
+      app: "gateway"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     networks:
       dmz:
       dcn:
@@ -81,44 +86,53 @@
     container_name: identitydb
     hostname: identitydb
     environment:
-      - ALLOW_EMPTY_PASSWORD=no
-      - POSTGRESQL_USERNAME=keycloak
-      - POSTGRESQL_DATABASE=keycloak
-      - POSTGRESQL_PASSWORD=keycloak
+      ALLOW_EMPTY_PASSWORD: no
+      POSTGRESQL_USERNAME: keycloak
+      POSTGRESQL_DATABASE: keycloak
+      POSTGRESQL_PASSWORD: keycloak
+    labels:
+      app: "identitydb"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
 
   identity:
     image: ${IDENTITY_IMAGE}
     container_name: identity
     hostname: identity
     environment:
-      - KEYCLOAK_CREATE_ADMIN_USER=true
-      - KEYCLOAK_ADMIN_USER=${ADMIN_USERNAME}
-      - KEYCLOAK_ADMIN_PASSWORD=${ADMIN_PASSWORD}
-      - KEYCLOAK_MANAGEMENT_USER=${IDENTITY_MGMT_USERNAME}
-      - KEYCLOAK_MANAGEMENT_PASSWORD=${IDENTITY_MGMT_PASSWORD}
-      - KEYCLOAK_DATABASE_HOST=identitydb
-      - KEYCLOAK_DATABASE_NAME=keycloak
-      - KEYCLOAK_DATABASE_USER=keycloak
-      - KEYCLOAK_DATABASE_PASSWORD=keycloak
-      - KEYCLOAK_JDBC_PARAMS=sslmode=disable&connectTimeout=30000
-      - KEYCLOAK_PRODUCTION=false
-      - KEYCLOAK_ENABLE_TLS=true
-      - KEYCLOAK_TLS_KEYSTORE_FILE=/opt/bitnami/keycloak/certs/keystore.jks
-      - KEYCLOAK_TLS_TRUSTSTORE_FILE=/opt/bitnami/keycloak/certs/truststore.jks
-      - KEYCLOAK_TLS_KEYSTORE_PASSWORD=password
-      - KEYCLOAK_TLS_TRUSTSTORE_PASSWORD=changeit
+      KEYCLOAK_CREATE_ADMIN_USER: true
+      KEYCLOAK_ADMIN_USER: ${ADMIN_USERNAME}
+      KEYCLOAK_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
+      KEYCLOAK_MANAGEMENT_USER: ${IDENTITY_MGMT_USERNAME}
+      KEYCLOAK_MANAGEMENT_PASSWORD: ${IDENTITY_MGMT_PASSWORD}
+      KEYCLOAK_DATABASE_HOST: identitydb
+      KEYCLOAK_DATABASE_NAME: keycloak
+      KEYCLOAK_DATABASE_USER: keycloak
+      KEYCLOAK_DATABASE_PASSWORD: keycloak
+      KEYCLOAK_JDBC_PARAMS=sslmode: disable&connectTimeout=30000
+      KEYCLOAK_PRODUCTION: false
+      KEYCLOAK_ENABLE_TLS: true
+      KEYCLOAK_TLS_KEYSTORE_FILE: /opt/bitnami/keycloak/certs/keystore.jks
+      KEYCLOAK_TLS_TRUSTSTORE_FILE: /opt/bitnami/keycloak/certs/truststore.jks
+      KEYCLOAK_TLS_KEYSTORE_PASSWORD: password
+      KEYCLOAK_TLS_TRUSTSTORE_PASSWORD: changeit
+      KEYCLOAK_EXTRA_ARGS: "--spi-theme-default=oam"
     restart: unless-stopped
     volumes:
       - /etc/localtime:/etc/localtime:ro
       - ./identity/standalone.xml:/opt/jboss/keycloak/standalone/configuration/standalone.xml
       - ./identity/keystore.jks:/opt/bitnami/keycloak/certs/keystore.jks
       - ./identity/truststoreONAPall.jks:/opt/bitnami/keycloak/certs/truststore.jks
+      - ./identity/themes/oam:/opt/bitnami/keycloak/themes/oam
     labels:
       traefik.enable: true
       traefik.http.routers.identity.entrypoints: websecure
       traefik.http.routers.identity.rule: Host(`identity.${HTTP_DOMAIN}`)
       traefik.http.routers.identity.tls: true
       traefik.http.services.identity.loadbalancer.server.port: 8080
+      app: "identity"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     depends_on:
       identitydb:
         condition: service_started
@@ -132,7 +146,31 @@
     image: ${PERSISTENCE_IMAGE}
     container_name: persistence
     environment:
-      - discovery.type=single-node
+      MARIADB_ROOT_PASSWORD: admin
+      MARIADB_DATABASE: sdnrdb
+      MARIADB_USER: sdnrdb
+      MARIADB_PASSWORD: sdnrdb
+      MARIADB_EXTRA_FLAGS: --bind-address=* --max_connections=400
+      MYSQL_ROOT_PASSWORD: admin
+      MYSQL_DATABASE: sdnrdb
+      MYSQL_USER: sdnrdb
+      MYSQL_PASSWORD: sdnrdb
+    labels:
+      app: "persistence"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
+    healthcheck:
+      interval: 30s
+      retries: 3
+      test:
+        [
+          "CMD",
+          "healthcheck.sh",
+          "--su-mysql",
+          "--connect",
+          "--innodb_initialized"
+        ]
+      timeout: 30s
 
   zookeeper:
     image: ${ZOOKEEPER_IMAGE}
@@ -150,6 +188,10 @@
       ZOOKEEPER_SERVER_ID:
     volumes:
       - ./zookeeper/zk_server_jaas.conf:/etc/zookeeper/secrets/jaas/zk_server_jaas.conf
+    labels:
+      app: "zookeeper"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
 
   kafka:
     image: ${KAFKA_IMAGE}
@@ -169,6 +211,10 @@
       KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
       # Reduced the number of partitions only to avoid the timeout error for the first subscribe call in slow environment
       KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS: 1
+    labels:
+      app: "kafka"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     volumes:
       - ./kafka/zk_client_jaas.conf:/etc/kafka/secrets/jaas/zk_client_jaas.conf
     depends_on:
@@ -192,6 +238,9 @@
       traefik.http.routers.kafka-bridge.rule: Host(`kafka-bridge.${HTTP_DOMAIN}`)
       traefik.http.routers.kafka-bridge.tls: true
       traefik.http.services.kafka-bridge.loadbalancer.server.port: 8080
+      app: "kafka-bridge"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     volumes:
       - ./kafka-bridge:/opt/strimzi/config
     depends_on:
@@ -222,6 +271,9 @@
       traefik.http.routers.topology.rule: Host(`topology.${HTTP_DOMAIN}`)
       traefik.http.routers.topology.tls: true
       traefik.http.services.topology.loadbalancer.server.port: 8181
+      app: "topology"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     networks:
       dmz:
       default:
@@ -242,6 +294,9 @@
       traefik.http.routers.messages.rule: Host(`messages.${HTTP_DOMAIN}`)
       traefik.http.routers.messages.tls: true
       traefik.http.services.messages.loadbalancer.server.port: 3904
+      app: "messages"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     depends_on:
       kafka:
         condition: service_started
diff --git a/solution/smo/common/identity/authentication.json b/solution/smo/common/identity/authentication.json
index 2f91979..868c790 100644
--- a/solution/smo/common/identity/authentication.json
+++ b/solution/smo/common/identity/authentication.json
@@ -1,111 +1,111 @@
 {
-  "users": [
-    {
-      "firstName": "Leia",
-      "lastName": "Organa",
-      "email": "leia.organa@sdnr.onap.org",
-      "enabled": "true",
-      "username": "leia.organa",
-      "credentials": [
+    "users": [
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "R2",
-      "lastName": "D2",
-      "email": "r2.d2@sdnr.onap.org",
-      "enabled": "true",
-      "username": "r2.d2",
-      "credentials": [
+            "firstName": "Leia",
+            "lastName": "Organa",
+            "email": "leia.organa@sdnr.onap.org",
+            "enabled": "",
+            "username": "leia.organa",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Luke",
-      "lastName": "Skywalker",
-      "email": "luke.skywalker@sdnr.onap.org",
-      "enabled": "true",
-      "username": "luke.skywalker",
-      "credentials": [
+            "firstName": "R2",
+            "lastName": "D2",
+            "email": "r2.d2@sdnr.onap.org",
+            "enabled": "",
+            "username": "r2.d2",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Jargo",
-      "lastName": "Fett",
-      "email": "jargo.fett@sdnr.onap.org",
-      "enabled": "true",
-      "username": "jargo.fett",
-      "credentials": [
+            "firstName": "Luke",
+            "lastName": "Skywalker",
+            "email": "luke.skywalker@sdnr.onap.org",
+            "enabled": "",
+            "username": "luke.skywalker",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Martin",
-      "lastName": "Skorupski",
-      "email": "martin.skorupski@highstreet-technologies.com",
-      "enabled": "true",
-      "username": "martin.skorupski",
-      "credentials": [
+            "firstName": "Jargo",
+            "lastName": "Fett",
+            "email": "jargo.fett@sdnr.onap.org",
+            "enabled": "",
+            "username": "jargo.fett",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
+            "firstName": "Martin",
+            "lastName": "Skorupski",
+            "email": "martin.skorupski@highstreet-technologies.com",
+            "enabled": "",
+            "username": "martin.skorupski",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
         }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    }
-  ],
-  "grants": [
-    {
-      "username": "leia.organa",
-      "role": "administration"
-    },
-    {
-      "username": "r2.d2",
-      "role": "administration"
-    },
-    {
-      "username": "luke.skywalker",
-      "role": "provision"
-    },
-    {
-      "username": "jargo.fett",
-      "role": "supervision"
-    },
-    {
-      "username": "martin.skorupski",
-      "role": "administration"
-    }
-  ]
+    ],
+    "grants": [
+        {
+            "username": "leia.organa",
+            "role": "administration"
+        },
+        {
+            "username": "r2.d2",
+            "role": "administration"
+        },
+        {
+            "username": "luke.skywalker",
+            "role": "provision"
+        },
+        {
+            "username": "jargo.fett",
+            "role": "supervision"
+        },
+        {
+            "username": "martin.skorupski",
+            "role": "administration"
+        }
+    ]
 }
\ No newline at end of file
diff --git a/solution/smo/common/identity/config.py b/solution/smo/common/identity/config.py
index 5a8bf44..37967e1 100644
--- a/solution/smo/common/identity/config.py
+++ b/solution/smo/common/identity/config.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
-#############################################################################
-# Copyright 2023 highstreet technologies GmbH
+################################################################################
+# Copyright 2021 highstreet technologies GmbH
 #
 # Licensed under the Apache License, Version 2.0 (the 'License');
 # you may not use this file except in compliance with the License.
@@ -16,26 +16,27 @@
 #
 
 # importing the sys, json, requests library
+from sqlite3 import TimeFromTicks
+from jproperties import Properties
 import os
-import pathlib
 import sys
 import json
 import time
-import getpass
-import requests
 import re
+import requests
+import getpass
 import warnings
-from jproperties import Properties
 from typing import List
+
 warnings.filterwarnings('ignore', message='Unverified HTTPS request')
+
+
 # global configurations
-
-
-def get_environment_variable(name):
+def get_env(name):
     configs = Properties()
-    path = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
-    env_file = str(path.parent.absolute()) + '/.env'
-    with open(env_file, "rb") as read_prop:
+    envFile = os.path.dirname(os.path.abspath(__file__)) + '/' + '../' + '.env'
+
+    with open(envFile, "rb") as read_prop:
         configs.load(read_prop)
     value = configs.get(name).data
 
@@ -45,54 +46,61 @@
         match = next(matches, None)
         if match is None:
             break
-        inner = get_environment_variable(match.group(1))
-        value = value.replace("${" + match.group(1) + "}", inner )
+        inner = get_env(match.group(1))
+        value = value.replace("${" + match.group(1) + "}", inner)
     return value
 
 
-def load_arguments(args: List[str]) -> tuple:
-    realm_file = os.path.dirname(os.path.abspath(
-        __file__)) + '/o-ran-sc-realm.json'
-    auth_file = os.path.dirname(os.path.abspath(
-        __file__)) + '/authentication.json'
-    ready_timeout = 180
+def loadArgs(args: List[str]) -> tuple:
+    realmFile = os.path.dirname(os.path.abspath(__file__)) + '/o-ran-sc-realm.json'
+    authFile = os.path.dirname(os.path.abspath(__file__)) + '/authentication.json'
+    readyTimeout = 180
     args.pop(0)
     while len(args) > 0:
         arg = args.pop(0)
         if arg == '--auth' and len(args) > 0:
-            auth_file = args.pop(0)
-            print('overwriting auth file: {}'.format(auth_file))
+            authFile = args.pop(0)
+            print('overwriting auth file: {}'.format(authFile))
         elif arg == '--realm' and len(args) > 0:
-            realm_file = args.pop(0)
-            print('overwriting realm file: {}'.format(realm_file))
+            realmFile = args.pop(0)
+            print('overwriting realm file: {}'.format(realmFile))
         elif arg == '--timeout' and len(args) > 0:
-            ready_timeout = int(args.pop(0))
-            print('waiting for ready {} seconds'.format(ready_timeout))
+            readyTimeout = int(args.pop(0))
+            print('waiting for ready {} seconds'.format(readyTimeout))
 
-    return (realm_file, auth_file, ready_timeout)
+    return (realmFile, authFile, readyTimeout)
 
 
 def isReady(timeoutSeconds=180):
     url = getBaseUrl()
-    print(f'url={url}')
+    print(url)
+    response = None
+    print("waiting for ready state", end='')
     while timeoutSeconds > 0:
         try:
             response = requests.get(url, verify=False, headers={})
+            print(response)
         except:
-            response = None
+            pass
         if response is not None and response.status_code == 200:
+            print('succeeded')
             return True
         time.sleep(1)
         timeoutSeconds -= 1
+        print('.', end='', flush=True)
     return False
 
 
 def getBaseUrl():
-    return get_environment_variable("IDENTITY_PROVIDER_URL")
-
-# Request a token for further communication
+    try:
+        if get_env("USE_LOCAL_HOST_FOR_IDENTITY_CONFIG").strip("'\"") == "true":
+            return get_env("IDENTITY_PROVIDER_URL_LOCAL_HOST")
+    except AttributeError:
+        print("Using IDENTITY_PROVIDER_URL")
+    return get_env("IDENTITY_PROVIDER_URL")
 
 
+# Request a token for futher communication
 def getToken():
     url = base + '/realms/master/protocol/openid-connect/token'
     headers = {
@@ -106,8 +114,7 @@
         'password': password
     }
     try:
-        response = requests.post(url, verify=False, auth=(
-            username, password), data=body, headers=headers)
+        response = requests.post(url, verify=False, auth=(username, password), data=body, headers=headers)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -122,9 +129,8 @@
     else:
         sys.exit('Getting token failed.')
 
+
 # create the default realm from file
-
-
 def createRealm(token, realm):
     url = base + '/admin/realms'
     auth = 'bearer ' + token
@@ -134,8 +140,7 @@
         'authorization': auth
     }
     try:
-        response = requests.post(
-            url, verify=False, json=realm, headers=headers)
+        response = requests.post(url, verify=False, json=realm, headers=headers)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -146,9 +151,8 @@
 
     return response.status_code >= 200 and response.status_code < 300
 
+
 # Check if default realm exists
-
-
 def checkRealmExists(token, realmId):
     url = base + '/admin/realms/' + realmId
     auth = 'bearer ' + token
@@ -172,9 +176,8 @@
         # sys.exit('Getting realm failed.')
         return False
 
+
 # create a user in default realm
-
-
 def createUser(token, realmConfig, user):
     realmId = realmConfig['id']
     url = base + '/admin/realms/' + realmId + '/users'
@@ -198,9 +201,8 @@
     else:
         print('User creation', user['username'], 'failed!\n', response.text)
 
+
 # creates User accounts in realm based a file
-
-
 def createUsers(token, realmConfig, authConfig):
     for user in authConfig['users']:
         createUser(token, realmConfig, user)
@@ -216,24 +218,24 @@
             {
                 "type": "password",
                 "value": password,
-                "temporary": True
+                "temporary": False
             }
-        ],
-        "requiredActions": [
-            "UPDATE_PASSWORD"
         ]
     }
     createUser(token, realmConfig, systemUser)
 
+
 # Grants a role to a user
-
-
-def addUserRole(user: dict, role: dict, options: dict):
+def addUserRole(user: dict, role: list, options: dict):
     url = options['url'] + '/' + user['id'] + '/role-mappings/realm'
     try:
-        response = requests.post(url, verify=False, json=[
-                                 {'id': role['id'], 'name':role['name']}],
-                                 headers=options['headers'])
+        for irole in role:
+            response = requests.post(url, verify=False, json=[{'id': irole['id'], 'name': irole['name']}],
+                                     headers=options['headers'])
+            if response.status_code >= 200 and response.status_code < 300:
+                print('User role', user['username'], irole['name'], 'created!')
+            else:
+                print('Creation of user role', user['username'], irole['name'], 'failed!\n', response.text)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -242,28 +244,24 @@
         # catastrophic error. bail.
         raise SystemExit(e)
 
-    if response.status_code >= 200 and response.status_code < 300:
-        print('User role', user['username'], role['name'], 'created!')
-    else:
-        print('Creation of user role',
-              user['username'], role['name'], 'failed!\n', response.text)
 
 # searches for the role of a given user
-
-
 def findRole(username: str, authConfig: dict, realmConfig: dict) -> dict:
+    roleList = []
+    roleNames = []
     roleName = 'administration'
     for grant in authConfig['grants']:
         if grant['username'] == username:
             roleName = grant['role']
-    for role in realmConfig['roles']['realm']:
-        if role['name'] == roleName:
-            return role
-    return None
+            roleNames = roleName.split(",")  # A user can have multiple roles, comma separated
+    for iroleName in roleNames:
+        for role in realmConfig['roles']['realm']:
+            if role['name'] == iroleName:
+                roleList.append(role)
+    return roleList
+
 
 # adds roles to users
-
-
 def addUserRoles(token, realmConfig, authConfig):
     realmId = realmConfig['id']
     url = base + '/admin/realms/' + realmId + '/users'
@@ -296,12 +294,12 @@
     else:
         sys.exit('Getting users failed.')
 
+
 # main
 
-
-(realmFile, authFile, readyTimeout) = load_arguments(sys.argv)
-username = get_environment_variable('ADMIN_USERNAME')
-password = get_environment_variable('ADMIN_PASSWORD')
+(realmFile, authFile, readyTimeout) = loadArgs(sys.argv)
+username = get_env('ADMIN_USERNAME')
+password = get_env('ADMIN_PASSWORD')
 base = getBaseUrl()
 isReady(readyTimeout)
 token = getToken()
diff --git a/solution/smo/common/identity/themes/README.md b/solution/smo/common/identity/themes/README.md
new file mode 100644
index 0000000..98d3823
--- /dev/null
+++ b/solution/smo/common/identity/themes/README.md
@@ -0,0 +1,6 @@
+# add themes to solution
+- copy `org.keycloak.keycloak-themes-XX.Y.Z.jar` from image and unzip 
+- copy keycloak themes into directory a themes subdirectory directory with 
+- modify css and resources (see dev resoures in keycloak)
+- use `- KEYCLOAK_EXTRA_ARGS="--log-level=DEBUG --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false"` for online development
+- add  `KEYCLOAK_EXTRA_ARGS="--spi-theme-default=5gberlin"` to as environment in docker-compose.yml for identity to select as default theme
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/README.md b/solution/smo/common/identity/themes/oam/README.md
new file mode 100644
index 0000000..17e8a14
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/README.md
@@ -0,0 +1,16 @@
+# create custom theme
+
+A detailed description of the theme creation can be found in the [keycloak documentation](https://www.keycloak.org/docs/latest/server_development/#_themes).
+It's not necessary to create a new theme from scratch. You can inherit from a base theme and override only the necessary 
+parts.
+
+- use `- KEYCLOAK_EXTRA_ARGS="--log-level=DEBUG --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false"` for theme development
+
+# add themes to solution
+
+After creating the theme, you can add it to the solution. The following steps are necessary:
+
+- mount the themes directory into the keycloak container
+  - target directory: `/opt/bitnami/keycloak/themes/[custom-theme-name]`
+- add  `KEYCLOAK_EXTRA_ARGS="--spi-theme-default=[custom-theme-name]"` to the environment section in docker-compose.yml 
+  for identity to select as default theme
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/account/theme.properties b/solution/smo/common/identity/themes/oam/account/theme.properties
new file mode 100644
index 0000000..12d9c19
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/account/theme.properties
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/admin/theme.properties b/solution/smo/common/identity/themes/oam/admin/theme.properties
new file mode 100644
index 0000000..12d9c19
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/admin/theme.properties
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/email/theme.properties b/solution/smo/common/identity/themes/oam/email/theme.properties
new file mode 100644
index 0000000..12d9c19
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/email/theme.properties
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/login/resources/css/styles.css b/solution/smo/common/identity/themes/oam/login/resources/css/styles.css
new file mode 100644
index 0000000..8cb33bc
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/login/resources/css/styles.css
@@ -0,0 +1,19 @@
+.login-pf body {
+    background: DimGrey none;
+}
+.login-pf body {
+    background: url("../img/o-ran-sc-smo-oam-keyclock-background.png") no-repeat center center fixed;
+    background-size: cover;
+    height: 100%;
+}
+div.kc-logo-text {
+    background-image: url(../img/o-ran-sc-logo.png);
+    background-repeat: no-repeat;
+    height: 63px;
+    width: 300px;
+    margin: 0 auto;
+}
+.card-pf {
+    background: #bbb;
+    opacity: 0.8;
+}
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png
new file mode 100644
index 0000000..c3b6ce5
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png
Binary files differ
diff --git a/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png
new file mode 100644
index 0000000..c8f24dd
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png
Binary files differ
diff --git a/solution/smo/common/identity/themes/oam/login/theme.properties b/solution/smo/common/identity/themes/oam/login/theme.properties
new file mode 100644
index 0000000..cad2a08
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/login/theme.properties
@@ -0,0 +1,3 @@
+parent=keycloak
+import=common/keycloak
+styles=css/login.css css/styles.css
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/welcome/theme.properties b/solution/smo/common/identity/themes/oam/welcome/theme.properties
new file mode 100644
index 0000000..12d9c19
--- /dev/null
+++ b/solution/smo/common/identity/themes/oam/welcome/theme.properties
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/oam/.env b/solution/smo/oam/.env
index 8541289..588a941 100644
--- a/solution/smo/oam/.env
+++ b/solution/smo/oam/.env
@@ -28,13 +28,14 @@
 IDENTITY_PROVIDER_URL=https://identity.${HTTP_DOMAIN}
 
 # SDN Controller
-SDNC_IMAGE=nexus3.onap.org:10001/onap/sdnc-image:2.4.2
+SDNC_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.6.1
 SDNC_REST_PORT=8181
+SDNR_WEBSOCKET_PORT=8182
 SDNC_CERT_DIR=/opt/opendaylight/current/certs
 SDNC_ENABLE_OAUTH=true
 
 # SDN Controller Web
-SDNC_WEB_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.4.2
+SDNC_WEB_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.6.1
 SDNC_WEB_PORT=8080
 
 ## VES Collector
diff --git a/solution/smo/oam/controller/mountpoint-registrar.properties b/solution/smo/oam/controller/mountpoint-registrar.properties
index 526c07a..7968841 100644
--- a/solution/smo/oam/controller/mountpoint-registrar.properties
+++ b/solution/smo/oam/controller/mountpoint-registrar.properties
@@ -4,35 +4,43 @@
 sdnrUser=admin
 sdnrPasswd=${ODL_ADMIN_PASSWORD}
 
-[fault]
-TransportType=HTTPNOAUTH
-host=messages:3904
-topic=unauthenticated.SEC_FAULT_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
-timeout=2000
-limit=1000
-fetchPause=5000
+[strimzi-kafka]
+strimziEnabled=true
+bootstrapServers=kafka:9092
+securityProtocol=PLAINTEXT
+saslMechanism=PLAIN
+saslJaasConfig=PLAIN
 
-[pnfRegistration]
-TransportType=HTTPNOAUTH
-host=messages:3904
-topic=unauthenticated.VES_PNFREG_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
-timeout=2000
-limit=1000
+
+[fault]
+topic=unauthenticated.SEC_FAULT_OUTPUT
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
 fetchPause=5000
 
 [provisioning]
-TransportType=HTTPNOAUTH
-host=messages:3904
 topic=unauthenticated.SEC_3GPP_PROVISIONING_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
+consumerGroup=myG
+consumerID=C1
 timeout=20000
 limit=10000
-fetchPause=5000
\ No newline at end of file
+fetchPause=5000
+
+[pnfRegistration]
+topic=unauthenticated.VES_PNFREG_OUTPUT
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
+fetchPause=5000
+
+[stndDefinedFault]
+topic=unauthenticated.SEC_3GPP_FAULTSUPERVISION_OUTPUT
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
+fetchPause=5000
+
diff --git a/solution/smo/oam/controller/oauth-aaa-app-config.xml b/solution/smo/oam/controller/oauth-aaa-app-config.xml
index c210e37..8acb414 100644
--- a/solution/smo/oam/controller/oauth-aaa-app-config.xml
+++ b/solution/smo/oam/controller/oauth-aaa-app-config.xml
@@ -15,37 +15,34 @@
   ~ 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=======================================================
-  ~
-  -->
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+~ ============LICENSE_END=======================================================
+    ~
+    -->
 
-<shiro-configuration xmlns="urn:opendaylight:aaa:app:config">
+        <shiro-configuration xmlns="urn:opendaylight:aaa:app:config">
 
 
     <main>
-        <pair-key>tokenAuthRealm</pair-key>
+    <pair-key>tokenAuthRealm</pair-key>
         <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.OAuth2Realm</pair-value>
-    </main>
+        </main>
 
     <main>
-        <pair-key>securityManager.realms</pair-key>
+    <pair-key>securityManager.realms</pair-key>
         <pair-value>$tokenAuthRealm</pair-value>
-    </main>
+        </main>
     <!-- Used to support OAuth2 use case. -->
     <main>
-        <pair-key>authcBasic</pair-key>
-        <pair-value>org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter</pair-value>
-    </main>
-    <main>
         <pair-key>anyroles</pair-key>
         <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.AnyRoleHttpAuthenticationFilter</pair-value>
-    </main>
+        </main>
     <main>
-        <pair-key>authcBearer</pair-key>
-        <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.BearerAndBasicHttpAuthenticationFilter</pair-value>
-    </main>
+    <pair-key>authcBearer</pair-key>
+    <!--        <pair-value>org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter</pair-value>-->
+    <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.BearerAndBasicHttpAuthenticationFilter</pair-value>
+        </main>
 
     <!-- in order to track AAA challenge attempts -->
     <main>
@@ -53,31 +50,34 @@
         <pair-value>org.opendaylight.aaa.shiro.filters.AuthenticationListener</pair-value>
     </main>
     <main>
-        <pair-key>securityManager.authenticator.authenticationListeners</pair-key>
-        <pair-value>$accountingListener</pair-value>
-    </main>
+    <pair-key>securityManager.authenticator.authenticationListeners</pair-key>
+    <pair-value>$accountingListener</pair-value>
+        </main>
 
     <!-- Model based authorization scheme supporting RBAC for REST endpoints -->
     <main>
-        <pair-key>dynamicAuthorization</pair-key>
-        <pair-value>org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter</pair-value>
-    </main>
+    <pair-key>dynamicAuthorization</pair-key>
+    <pair-value>org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter</pair-value>
+        </main>
 
 
     <urls>
         <pair-key>/**/operations/cluster-admin**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
     <urls>
         <pair-key>/**/v1/**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
-    <!-- allow admin only access to write mdsal auth config -->
     <urls>
-        <pair-key>/rests/**/aaa*/**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+    <pair-key>/rests/**/aaa*/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+
+    <urls>
+        <pair-key>/**/config/aaa*/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
-    <!-- anon access for login api -->
     <urls>
         <pair-key>/oauth/**</pair-key>
         <pair-value>anon</pair-value>
@@ -86,23 +86,29 @@
         <pair-key>/ready</pair-key>
         <pair-value>anon</pair-value>
     </urls>
-    <!-- anon access for odlux ui -->
     <urls>
-        <pair-key>/odlux/**</pair-key>
-        <pair-value>anon</pair-value>
-    </urls>
-    <!-- admin only access for apidocs -->
+    <pair-key>/apidoc/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+    <!-- these two rules are needed for installCerts.py -->
     <urls>
-        <pair-key>/apidoc/**</pair-key>
+        <pair-key>/rests/data/network-topology:network-topology</pair-key>
         <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
     <urls>
-        <pair-key>/rests/**</pair-key>
-        <pair-value>authcBearer, anyroles["admin,provision"]</pair-value>
-    </urls>
-    <!-- any other access with configured dynamic filter -->
+    <pair-key>/rests/operations/netconf-keystore*</pair-key>
+    <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+
+    <!-- rfc8040 restconf access with configured dynamic filter -->
     <urls>
-        <pair-key>/**</pair-key>
-        <pair-value>authcBearer, anyroles["admin,provision"]</pair-value>
-    </urls>
+    <pair-key>/rests/**</pair-key>
+        <pair-value>authcBearer, dynamicAuthorization</pair-value>
+        </urls>
+    <!-- any other access with configured dynamic filter -->
+<urls>
+<pair-key>/**</pair-key>
+<pair-value>authcBearer, roles[admin]</pair-value>
+</urls>
 </shiro-configuration>
+
diff --git a/solution/smo/oam/docker-compose.yaml b/solution/smo/oam/docker-compose.yaml
index 0042d22..bc827ae 100755
--- a/solution/smo/oam/docker-compose.yaml
+++ b/solution/smo/oam/docker-compose.yaml
@@ -15,8 +15,8 @@
 #
 
 # no more versions needed! Compose spec supports all features w/o a version
+version: "3.8"
 services:
-
   odlux:
     image: ${SDNC_WEB_IMAGE}
     container_name: odlux
@@ -28,12 +28,16 @@
       SDNRPROTOCOL: http
       SDNRHOST: controller
       SDNRPORT: ${SDNC_REST_PORT}
+      SDNRWEBSOCKETPORT: ${SDNR_WEBSOCKET_PORT}
     labels:
       traefik.enable: true
       traefik.http.routers.sdnc-web.entrypoints: websecure
       traefik.http.routers.sdnc-web.rule: Host(`odlux.oam.${HTTP_DOMAIN}`)
       traefik.http.routers.sdnc-web.tls: true
       traefik.http.services.sdnc-web.loadbalancer.server.port: ${SDNC_WEB_PORT}
+      app: "odlux"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     depends_on:
       controller:
         condition: service_healthy
@@ -56,6 +60,7 @@
     environment:
       ENABLE_ODL_CLUSTER: false
       ENABLE_OAUTH: ${SDNC_ENABLE_OAUTH}
+      ENABLE_ODLUX_RBAC: false
       ODL_CERT_DIR: ${SDNC_CERT_DIR}
       ODL_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
       SDNC_CONFIG_DIR: /opt/onap/ccsdk/data/properties
@@ -66,10 +71,14 @@
       SDNRONLY: true
       SDNRINIT: true
       SDNRDM: true
-      SDNRDBURL: http://persistence:9200
+      SDNRDBTYPE: MARIADB
+      SDNRDBURL: jdbc:mysql://persistence:3306/sdnrdb
+      SDNRDBUSERNAME: sdnrdb
+      SDNRDBPASSWORD: sdnrdb
       SDNR_NETCONF_CALLHOME_ENABLED: true
       A1_ADAPTER_NORTHBOUND: false
       JAVA_OPTS: -Xms256m -Xmx4g
+      SDNR_WEBSOCKET_PORT: ${SDNR_WEBSOCKET_PORT}
       IDENTITY_PROVIDER_URL: ${IDENTITY_PROVIDER_URL}
       SDNC_WEB_URL: https://odlux.oam.${HTTP_DOMAIN}
       SDNR_VES_COLLECTOR_ENABLED: true
@@ -107,6 +116,9 @@
       traefik.tcp.routers.controller-tls.tls: false
       traefik.tcp.routers.controller-tls.service: controller-tls
       traefik.tcp.services.controller-tls.loadbalancer.server.port: 4335
+      app: "controller"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     networks:
       smo:
       dcn:
@@ -117,6 +129,7 @@
       context: ./ves-collector
       args:
         - BASEIMAGE=${VES_COLLECTOR_IMAGE}
+      network: host
     container_name: ves-collector
     hostname: ves-collector
     extra_hosts:
@@ -138,6 +151,9 @@
       traefik.http.routers.ves.rule: Host(`ves-collector.dcn.${HTTP_DOMAIN}`)
       traefik.http.routers.ves.tls: true
       traefik.http.services.ves.loadbalancer.server.port: ${VES_ENDPOINT_PORT}
+      app: "ves-collector"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     networks:
       smo:
       dcn: