-FROM nexus3.onap.org:10001/onap/integration-python:12.0.0
+FROM nexus3.onap.org:10001/onap/integration-python:12.0.0 as build
# https://nexus3.onap.org/#browse/search=keyword%3Dintegration-python:d406d405e4cfbf1186265b01088caf9a
# https://git.onap.org/integration/docker/onap-python/tree/Dockerfile
USER root
-ARG user=orano2
-ARG group=orano2
-# Create a group and user
-RUN addgroup -S $group && adduser -S -D -h /home/$user $user $group && \
- chown -R $user:$group /home/$user && \
- mkdir /var/log/$user && \
- mkdir -p /src && \
- mkdir -p /configs/ && \
- mkdir -p /src/o2app/ && \
- mkdir -p /src/helm_sdk/ && \
- mkdir -p /etc/o2/ && \
- chown -R $user:$group /var/log/$user && \
- chown -R $user:$group /src && \
- chown -R $user:$group /configs && \
- chown -R $user:$group /etc/o2/
+RUN apk add --no-cache \
+ git \
+ curl \
+ bluez-dev \
+ bzip2-dev \
+ dpkg-dev dpkg \
+ expat-dev \
+ gcc \
+ libc-dev \
+ libffi-dev \
+ libnsl-dev \
+ libtirpc-dev \
+ linux-headers \
+ make \
+ ncurses-dev \
+ openssl-dev \
+ pax-utils \
+ sqlite-dev \
+ tcl-dev \
+ tk \
+ tk-dev \
+ util-linux-dev \
+ xz-dev \
+ zlib-dev \
+ && curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz \
+ && tar -zxvf helm-v3.3.1-linux-amd64.tar.gz \
+ && cp linux-amd64/helm /usr/local/bin \
+ && rm -f helm-v3.3.1-linux-amd64.tar.gz
COPY requirements.txt /tmp/
COPY requirements-stx.txt /tmp/
COPY constraints.txt /tmp/
+COPY setup.py /src/
+ENV PATH="/.venv/bin:${PATH}"
-COPY o2ims/ /src/o2ims/
-COPY o2dms/ /src/o2dms/
-COPY o2common/ /src/o2common/
-COPY o2app/ /src/o2app/
-COPY setup.py /src/
+RUN mkdir -p /.venv && \
+ python -m venv /.venv \
+ && pip install --no-cache-dir -r /tmp/requirements.txt -r /tmp/requirements-stx.txt -c /tmp/constraints.txt \
+ && pip install --no-cache-dir -e /src
+
+FROM nexus3.onap.org:10001/onap/integration-python:12.0.0
+
+ARG user=orano2
+ARG group=orano2
+
+USER root
+
+RUN apk add --no-cache bash
+
+COPY --from=build /.venv /.venv
+COPY --from=build /src /src
+
+# Create a group and user
+RUN addgroup -S $group \
+ && adduser -S -D -h /home/$user $user $group \
+ && chown -R $user:$group /home/$user \
+ && mkdir /var/log/$user \
+ && mkdir -p /src \
+ && mkdir -p /configs/ \
+ && mkdir -p /src/o2app/ \
+ && mkdir -p /src/helm_sdk/ \
+ && mkdir -p /etc/o2/ \
+ && chown -R $user:$group /var/log/$user \
+ && chown -R $user:$group /src \
+ && chown -R $user:$group /configs \
+ && chown -R $user:$group /etc/o2/
COPY helm_sdk/ /src/helm_sdk/
COPY configs/ /etc/o2/
COPY configs/ /configs/
-RUN set -ex \
- && apk add --no-cache bash \
- && apk add --no-cache --virtual .fetch2-deps \
- git curl \
- && apk add --no-cache --virtual .build2-deps \
- bluez-dev \
- bzip2-dev \
- dpkg-dev dpkg \
- expat-dev \
- gcc \
- libc-dev \
- libffi-dev \
- libnsl-dev \
- libtirpc-dev \
- linux-headers \
- make \
- ncurses-dev \
- openssl-dev \
- pax-utils \
- sqlite-dev \
- tcl-dev \
- tk \
- tk-dev \
- util-linux-dev \
- xz-dev \
- zlib-dev \
- && pip install -r /tmp/requirements.txt -r /tmp/requirements-stx.txt -c /tmp/constraints.txt \
- && curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz; \
- tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; \
- cp linux-amd64/helm /usr/local/bin; \
- rm -f helm-v3.3.1-linux-amd64.tar.gz \
- && pip install -e /src \
- && apk del --no-network .fetch2-deps \
- && apk del --no-network .build2-deps
-
-# && pip install -r /tmp/requirements.txt -r /tmp/requirements-stx.txt -c /tmp/constraints.txt
-# RUN apt-get update && apt-get install -y git gcc procps vim curl ssh
-# && git clone --depth 1 --branch r/stx.7.0 https://opendev.org/starlingx/config.git /cgtsclient \
-# && git clone --depth 1 --branch r/stx.7.0 https://opendev.org/starlingx/distcloud-client.git /distcloud-client/ \
-# && git clone --depth 1 --branch r/stx.7.0 https://opendev.org/starlingx/fault.git /faultclient \
-# && pip install -e /cgtsclient/sysinv/cgts-client/cgts-client \
-# && pip install -e /distcloud-client/distributedcloud-client \
-# && pip install -e /faultclient/python-fmclient/fmclient \
-# && rm -rf /cgtsclient /distcloud-client /faultclient
+COPY o2common/ /src/o2common/
+COPY o2app/ /src/o2app/
+COPY o2dms/ /src/o2dms/
+COPY o2ims/ /src/o2ims/
WORKDIR /src
# USER $user
ENV PYTHONHASHSEED=0
+ENV PATH="/.venv/bin:${PATH}"
-FROM python:3.11-slim-buster
-
-RUN apt-get update && apt-get install -y git gcc \
- vim curl procps ssh
+FROM nexus3.onap.org:10001/onap/integration-python:12.0.0
+# https://nexus3.onap.org/#browse/search=keyword%3Dintegration-python:d406d405e4cfbf1186265b01088caf9a
+# https://git.onap.org/integration/docker/onap-python/tree/Dockerfile
+
+USER root
+
+ARG user=orano2
+ARG group=orano2
+
+# Create a group and user
+RUN addgroup -S $group && adduser -S -D -h /home/$user $user $group && \
+ chown -R $user:$group /home/$user && \
+ mkdir /var/log/$user && \
+ mkdir -p /src && \
+ mkdir -p /configs/ && \
+ mkdir -p /src/o2app/ && \
+ mkdir -p /src/helm_sdk/ && \
+ mkdir -p /etc/o2/ && \
+ chown -R $user:$group /var/log/$user && \
+ chown -R $user:$group /src && \
+ chown -R $user:$group /configs && \
+ chown -R $user:$group /etc/o2/
+
+COPY requirements.txt requirements-test.txt requirements-stx.txt constraints.txt /tmp/
# in case git repo is not accessable
RUN mkdir -p /cgtsclient && mkdir -p /distcloud-client
COPY temp/distcloud-client /distcloud-client/
COPY temp/fault /faultclient/
-RUN pip install -e cgtsclient/sysinv/cgts-client/cgts-client/ \
- && pip install -e /distcloud-client/distributedcloud-client \
- && pip install -e /faultclient/python-fmclient/fmclient/
-# in case git repo is not accessable
-
-COPY requirements.txt constraints.txt requirements-test.txt /tmp/
-
-RUN pip install -r /tmp/requirements.txt -c /tmp/constraints.txt \
- && pip install -r /tmp/requirements-test.txt
-
-
-RUN mkdir -p /src
COPY o2ims/ /src/o2ims/
COPY o2dms/ /src/o2dms/
COPY o2common/ /src/o2common/
-
-RUN mkdir -p /src/o2app/
COPY o2app/ /src/o2app/
+COPY setup.py /src/
-RUN mkdir -p /src/helm_sdk/
COPY helm_sdk/ /src/helm_sdk/
-COPY setup.py /src/
-
COPY configs/ /etc/o2/
+COPY configs/ /configs/
+
+RUN apk add --no-cache \
+ git \
+ curl \
+ bluez-dev \
+ bzip2-dev \
+ dpkg-dev dpkg \
+ expat-dev \
+ gcc \
+ libc-dev \
+ libffi-dev \
+ libnsl-dev \
+ libtirpc-dev \
+ linux-headers \
+ make \
+ ncurses-dev \
+ openssl-dev \
+ pax-utils \
+ sqlite-dev \
+ tcl-dev \
+ tk \
+ tk-dev \
+ util-linux-dev \
+ xz-dev \
+ zlib-dev
+
+RUN set -ex \
+ && apk add --no-cache bash \
+ && apk add --no-cache --virtual .fetch2-deps \
+ && pip install -r /tmp/requirements.txt -c /tmp/constraints.txt \
+ && pip install -r /tmp/requirements-test.txt \
+ && pip install -e /cgtsclient/sysinv/cgts-client/cgts-client/ \
+ && pip install -e /distcloud-client/distributedcloud-client \
+ && pip install -e /faultclient/python-fmclient/fmclient/ \
+ && pip install -e /src \
+ && apk del --no-network .fetch2-deps
-# RUN mkdir -p /helmsdk
-# COPY temp/helmsdk /helmsdk/
-# # RUN git clone --depth 1 --branch master https://github.com/cloudify-incubator/cloudify-helm-plugin.git helmsdk
-# COPY /helmsdk/helm_sdk /src/helm_sdk
-
-# RUN pip install -e /src
COPY tests/ /tests/
-#RUN apt-get install -y procps vim
-
-#RUN apt-get install -y curl
-RUN curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz;
-RUN tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin
-
RUN mkdir -p /etc/kubeconfig/
# COPY temp/kubeconfig/config /etc/kubeconfig/
RUN mkdir -p /var/log/orano2
WORKDIR /src
+
+# USER $user
+ENV PYTHONHASHSEED=0
#!/bin/bash
# The gunicorn start with [::] to listen on both IPv4 and IPv6
-gunicorn -b [::]:80 o2app.entrypoints.flask_application:app --certfile /configs/server.crt --keyfile /configs/server.key
+gunicorn -b [::]:80 o2app.entrypoints.flask_application:app \
+--certfile /configs/server.crt \
+--keyfile /configs/server.key \
+--ca-certs /configs/smoca.crt \
+--cert-reqs 2
sleep infinity
volumeMounts:
- name: scripts
mountPath: /opt
+ {{- if .Values.db.persistence }}
- name: db-pv
mountPath: /var/lib/postgresql/data
+ {{- end }}
- name: redis
image: "{{ .Values.o2ims.images.tags.redis }}"
ports:
value: "1"
- name: REDIS_HOST
value: localhost
+ {{- if default false .Values.o2ims.useHostCert }}
+ - name: REQUESTS_CA_BUNDLE
+ value: /etc/ssl/custom-cert.pem
+ {{- end }}
+ - name: CGTS_INSECURE_SSL
+ value: {{ ternary "1" "0" (default false .Values.o2ims.cgtsInsecureSSL) | quote }}
volumeMounts:
- name: scripts
mountPath: /opt
mountPath: /configs/o2app.conf
subPath: config.json
readOnly: true
+ {{- if default false .Values.o2ims.useHostCert }}
+ - name: ca-certs
+ mountPath: /etc/ssl/custom-cert.pem
+ readOnly: true
+ {{- end }}
- name: o2api
image: "{{ .Values.o2ims.images.tags.o2service }}"
ports:
mountPath: /configs/server.key
subPath: config.json
readOnly: true
+ - name: smocacrt
+ mountPath: /configs/smoca.crt
+ subPath: config.json
{{- if .Values.o2dms.helm_cli_enable }}
- name: helmcli
image: "{{ .Values.o2ims.images.tags.o2service }}"
- configMap:
name: {{ .Chart.Name }}-smocacrt
name: smocacrt
+ {{- if .Values.db.persistence }}
- name: db-pv
persistentVolumeClaim:
claimName: {{ .Chart.Name }}-db-pv
+ {{- end }}
+ {{- if default false .Values.o2ims.useHostCert }}
+ - name: ca-certs
+ hostPath:
+ path: {{ .Values.o2ims.hostCertPath | quote }}
+ type: File
+ {{- end }}
---
-# Copyright (C) 2021-2023 Wind River Systems, Inc.
+---
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
namespace: oran-o2
db:
+ persistence: true
storageSize: 10Gi
# ImagePullSecrets for operator ServiceAccount, list of secrets in the same
pullPolicy: IfNotPresent
logginglevel: "WARNING"
+ # Mount certs from host system.
+ # Normally required to use CGTS client with SSL.
+ useHostCert: false
+ hostCertPath: /etc/ssl/certs/ca-certificates.crt
+
+ # Skip SSL verification when using CGTS client.
+ cgtsInsecureSSL: false
+
o2dms:
helm_cli_enable: false
smo_register_url = http://127.0.0.1:8090/register
smo_token_data = smo_token_payload
+auth_provider = oauth2
+
+[OAUTH2]
+# support OAuth2.0
+
+# oauth2 token verify type: jwt or introspection
+oauth2_verify_type =
+# oauth2 public key
+oauth2_public_key =
+# oauth2 encryption asymmetric algorithm
+oauth2_algorithm =
+
+# oauth2 jwt token introspection endpoint, required if oauth2_verify_type = introspection
+oauth2_introspection_endpoint =
+# required if oauth2_verify_type = introspection
+oauth2_client_id =
+# required if oauth2_verify_type = introspection
+oauth2_client_secret =
+
[OCLOUD]
-OS_AUTH_URL =
-OS_USERNAME =
-OS_PASSWORD =
-API_HOST_EXTERNAL_FLOATING =
+OS_AUTH_URL =
+OS_USERNAME =
+OS_PASSWORD =
+API_HOST_EXTERNAL_FLOATING =
[API]
# support native_k8sapi,sol018,sol018_helmcli
# if the value is black, then native_k8sapi will set by default
-DMS_SUPPORT_PROFILES =
+DMS_SUPPORT_PROFILES =
[WATCHER]
- OS_USERNAME=${OS_USERNAME}
- OS_PASSWORD=${OS_PASSWORD}
- LOGGING_CONFIG_LEVEL=DEBUG
+ - CGTS_INSECURE_SSL=1
volumes:
- ./configs:/configs
- ./o2ims:/o2ims
- OS_PASSWORD=${OS_PASSWORD}
- LOGGING_CONFIG_LEVEL=DEBUG
- HELM_USER_PASSWD=St8rlingX*
+ - CGTS_INSECURE_SSL=1
volumes:
- ./configs:/configs
- ./share:/share
- "5005:80"
watcher:
- build:
- context: .
- dockerfile: Dockerfile.localtest
image: o2imsdms
depends_on:
- redis_pubsub
- OS_USERNAME=${OS_USERNAME}
- OS_PASSWORD=${OS_PASSWORD}
- LOGGING_CONFIG_LEVEL=DEBUG
+ - CGTS_INSECURE_SSL=1
volumes:
- ./configs:/configs
- ./o2ims:/o2ims
from o2common.authmw import authmiddleware
from o2common.authmw import authprov
-from o2common.config.config import get_review_url
from o2common.helper import o2logging
+AUTH_ENABLED = True
+FLASK_API_VERSION = '1.0.0'
+
# apibase = config.get_o2ims_api_base()
-auth = True
app = Flask(__name__)
logger = o2logging.get_logger(__name__)
-
-def _get_k8s_url():
- try:
- token_review_url = get_review_url()
- return token_review_url
- except Exception:
- raise Exception('Get k8s token review url failed')
-
-
-FLASK_API_VERSION = '1.0.0'
-
-if auth:
+if AUTH_ENABLED:
# perform service account identity&privilege check.
- _get_k8s_url()
ad = authprov.auth_definer('ad')
ad.sanity_check()
app.wsgi_app = authmiddleware.authmiddleware(app.wsgi_app)
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
import redis
import json
+
from o2app import bootstrap
-from o2common.config import config
from o2common.adapter.notifications import SmoNotifications
+from o2common.config import config
+from o2common.helper import o2logging
from o2dms.domain import commands
from o2ims.domain import commands as imscmd
from o2ims.domain.subscription_obj import Message2SMO, RegistrationMessage
from o2ims.domain.alarm_obj import AlarmEvent2SMO
-from o2common.helper import o2logging
logger = o2logging.get_logger(__name__)
r = redis.Redis(**config.get_redis_host_and_port())
pubsub.subscribe('DmsChanged')
pubsub.subscribe('ResourceChanged')
pubsub.subscribe('AlarmEventChanged')
+ pubsub.subscribe('AlarmEventPurged')
for m in pubsub.listen():
try:
eventtype=data['notificationEventType'],
updatetime=data['updatetime']))
bus.handle(cmd)
+ elif channel == 'AlarmEventPurged':
+ datastr = m['data']
+ data = json.loads(datastr)
+ logger.info('AlarmEventPurged with cmd:{}'.format(data))
+ cmd = imscmd.PurgeAlarmEvent(data=AlarmEvent2SMO(
+ id=data['id'], eventtype=data['notificationEventType'],
+ updatetime=data['updatetime']))
+ bus.handle(cmd)
else:
logger.info("unhandled:{}".format(channel))
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
pserver_dev_handler, agg_compute_handler, agg_network_handler,\
agg_storage_handler, agg_undefined_handler
from o2ims.service.command import notify_handler, registration_handler,\
- notify_alarm_handler
+ notify_alarm_handler, purge_alarm_handler
from o2ims.service.event import ocloud_event, resource_event, \
resource_pool_event, alarm_event, dms_event, resource_type_event
notify_resourcepool_change],
events.AlarmEventChanged: [alarm_event.\
notify_alarm_event_change],
+ events.AlarmEventPurged: [alarm_event.\
+ notify_alarm_event_purge],
} # type: Dict[Type[events.Event], Callable]
commands.PubMessage2SMO: notify_handler.notify_change_to_smo,
commands.PubAlarm2SMO: notify_alarm_handler.notify_alarm_to_smo,
commands.Register2SMO: registration_handler.registry_to_smo,
+ commands.PurgeAlarmEvent: purge_alarm_handler.purge_alarm_event,
} # type: Dict[Type[commands.Command], Callable]
# See the License for the specific language governing permissions and
# limitations under the License.
+import json
+from flask_restx._http import HTTPStatus
from werkzeug.wrappers import Request, Response
-from o2common.helper import o2logging
+
from o2common.authmw.authprov import auth_definer
-from flask_restx._http import HTTPStatus
-import json
+from o2common.authmw.exceptions import AuthRequiredExp
+from o2common.authmw.exceptions import AuthFailureExp
+from o2common.helper import o2logging
logger = o2logging.get_logger(__name__)
-class AuthRequiredExp(Exception):
- def __init__(self, value):
- self.value = value
-
- def dictize(self):
- return {
- 'WWW-Authenticate': '{}'.format(self.value)}
-
-
class AuthProblemDetails():
def __init__(self, code: int, detail: str, path: str,
title=None, instance=None
return json.dumps(details, indent=True)
-class AuthFailureExp(Exception):
- def __init__(self, value):
- self.value = value
-
- def dictize(self):
- return {
- 'WWW-Authenticate': '{}'.format(self.value)}
-
-
def _response_wrapper(environ, start_response, header, detail):
res = Response(headers=header,
mimetype='application/json', status=401, response=detail)
class authmiddleware():
-
'''
Auth WSGI middleware
'''
# limitations under the License.
import ssl
-from o2common.helper import o2logging
import urllib.request
import urllib.parse
import json
-
+from http import HTTPStatus
+from requests import post as requests_post
+from requests.auth import HTTPBasicAuth
+from requests.exceptions import HTTPError
+from jwt import decode as jwt_decode
+from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
+
+from o2common.authmw.exceptions import AuthRequiredExp
+from o2common.authmw.exceptions import AuthFailureExp
from o2common.config.config import get_auth_provider, get_review_url
from o2common.config.config import get_reviewer_token
+from o2common.config import conf
+from o2common.helper import o2logging
ssl._create_default_https_context = ssl._create_unverified_context
logger = o2logging.get_logger(__name__)
-# read the conf from config file
-auth_prv_conf = get_auth_provider()
-try:
- token_review_url = get_review_url()
-except Exception:
- raise Exception('Get k8s token review url failed')
+class OAuthAuthenticationException(Exception):
+ def __init__(self, value):
+ self.value = value
class K8SAuthenticaException(Exception):
def __init__(self, name):
super().__init__()
self.name = name
+ # read the conf from config file
+ auth_prv_conf = get_auth_provider()
if auth_prv_conf == 'k8s':
self.obj = k8s_auth_provider('k8s')
else:
- self.obj = keystone_auth_provider('keystone')
+ self.obj = oauth2_auth_provider('oauth2')
def tokenissue(self):
return self.obj.tokenissue()
def sanity_check(self):
return self.obj.sanity_check()
- # call k8s api
def authenticate(self, token):
return self.obj.authenticate(token)
def __init__(self, name):
self.name = name
+ try:
+ self.token_review_url = get_review_url()
+ except Exception:
+ raise Exception('Failed to get k8s token review url.')
def tokenissue(self, **args2):
pass
raise Exception(str(ex))
def authenticate(self, token):
+ ''' Call Kubenetes API to authenticate '''
reviewer_token = get_reviewer_token()
tokenreview = {
"kind": "TokenReview",
'Content-Type': 'application/json'}
try:
req = urllib.request.Request(
- token_review_url, data=binary_data, headers=header)
+ self.token_review_url, data=binary_data, headers=header)
response = urllib.request.urlopen(req)
data = json.load(response)
if data['status']['authenticated'] is True:
return True
-class keystone_auth_provider(auth_definer):
+class oauth2_auth_provider(auth_definer):
def __init__(self, name):
self.name = name
- def tokenissue(self, *args1, **args2):
- pass
+ def _format_public_key(self):
+ public_key_string = """-----BEGIN PUBLIC KEY----- \
+ %s \
+ -----END PUBLIC KEY-----""" % conf.OAUTH2.oauth2_public_key
+ return public_key_string
+
+ def _verify_jwt_token_introspect(self, token):
+ introspect_endpoint = conf.OAUTH2.oauth2_introspection_endpoint
+ client_id = conf.OAUTH2.oauth2_client_id
+ client_secret = conf.OAUTH2.oauth2_client_secret
+ try:
+ response = requests_post(
+ introspect_endpoint,
+ data={'token': token, 'client_id': client_id},
+ auth=HTTPBasicAuth(client_id, client_secret)
+ )
+ except HTTPError as e:
+ logger.error('OAuth2 jwt token introspect verify failed.')
+ raise Exception(str(e))
+ if response.status_code == HTTPStatus.OK:
+ introspection_data = response.json()
+ if introspection_data.get('active'):
+ logger.info('OAuth2 jwt token introspect result active.')
+ return True
+ logger.info('OAuth2 jwt token introspect verify failed.')
+ return False
+
+ def _verify_jwt_token(self, token):
+ algorithm = conf.OAUTH2.oauth2_algorithm
+ public_key_string = self._format_public_key()
+ try:
+ options = {"verify_signature": True, "verify_aud": False,
+ "exp": True}
+ decoded_token = jwt_decode(token, public_key_string,
+ algorithms=[algorithm], options=options)
+ logger.info(
+ 'Verified Token from client: %s' %
+ decoded_token.get("clientHost"))
+ return True
+ except (ExpiredSignatureError,
+ InvalidTokenError) as e:
+ logger.error(f'OAuth2 jwt token validation failed: {e}')
+ raise AuthFailureExp(
+ 'OAuth2 JWT Token Authentication failure.')
+ except Exception as e:
+ raise AuthRequiredExp(str(e))
- def authenticate(self, *args1, **args2):
+ def authenticate(self, token):
+ ''' Call the JWT to authenticate
+
+ If the verify type is introspection, call introspection endpoint to
+ verify the token.
+ If the verify type is jwt, call JWT SDK to verify the token.
+ '''
+ oauth2_verify_type = conf.OAUTH2.oauth2_verify_type
+ if oauth2_verify_type == 'introspection':
+ return self._verify_jwt_token_introspect(token)
+ elif oauth2_verify_type == 'jwt':
+ return self._verify_jwt_token(token)
return False
def sanity_check(self):
pass
-
- def tokenrevoke(self, *args1, **args2):
- return False
--- /dev/null
+# Copyright (C) 2024 Wind River Systems, Inc.
+#
+# 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.
+
+# pylint: disable=too-few-public-methods
+
+
+class AuthGeneralException(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def dictize(self):
+ return {
+ 'WWW-Authenticate': '{}'.format(self.value)}
+
+
+class AuthRequiredExp(AuthGeneralException):
+ pass
+
+
+class AuthFailureExp(AuthGeneralException):
+ pass
logger = o2logging.get_logger(__name__)
-_DEFAULT_DCMANAGER_URL = "http://192.168.204.1:8119/v1.0"
+CGTS_INSECURE_SSL = os.environ.get("CGTS_INSECURE_SSL", "0") == "1"
+
_DEFAULT_STX_URL = "http://192.168.204.1:5000/v3"
+_DCMANAGER_URL_PORT = os.environ.get("DCMANAGER_API_PORT", "8119")
+_DCMANAGER_URL_PATH = os.environ.get("DCMANAGER_API_PATH", "/v1.0")
def get_config_path():
return f"https://{host}:{port}"
+def get_stx_url():
+ try:
+ return get_stx_client_args()["auth_url"]
+ except KeyError:
+ logger.error('Please source your RC file before execution, '
+ 'e.g.: `source ~/downloads/admin-rc.sh`')
+ sys.exit(1)
+
+
+def get_dc_manager_url():
+ auth_url = os.environ.get("DCMANAGER_OS_AUTH_URL", None)
+ if auth_url is None:
+ temp_url = get_stx_url()
+ u = urlparse(temp_url)
+ u = u._replace(netloc=f"{u.hostname}:{_DCMANAGER_URL_PORT}")
+ u = u._replace(path=_DCMANAGER_URL_PATH)
+ auth_url = u.geturl()
+ return auth_url
+
+
def get_root_api_base():
return "/"
def get_stx_access_info(region_name="RegionOne", subcloud_hostname: str = "",
sub_is_https: bool = False):
- # authurl = os.environ.get("STX_AUTH_URL", "http://192.168.204.1:5000/v3")
- # username = os.environ.get("STX_USERNAME", "admin")
- # pswd = os.environ.get("STX_PASSWORD", "passwd1")
- # stx_access_info = (authurl, username, pswd)
try:
- # client_args = dict(
- # auth_url=os.environ.get('OS_AUTH_URL', _DEFAULT_STX_URL),
- # username=os.environ.get('OS_USERNAME', "admin"),
- # api_key=os.environ.get('OS_PASSWORD', "fakepasswd1"),
- # project_name=os.environ.get('OS_PROJECT_NAME', "admin"),
- # )
client_args = get_stx_client_args()
except KeyError:
logger.error('Please source your RC file before execution, '
if "" != subcloud_hostname:
if is_ipv6(subcloud_hostname):
subcloud_hostname = "[" + subcloud_hostname + "]"
- orig_auth_url = urlparse(_DEFAULT_STX_URL)
+ orig_auth_url = urlparse(get_stx_url())
new_auth_url = orig_auth_url._replace(
netloc=orig_auth_url.netloc.replace(
orig_auth_url.hostname, subcloud_hostname))
new_auth_url = new_auth_url._replace(
scheme=new_auth_url.scheme.
replace(new_auth_url.scheme, 'https'))
- os_client_args['insecure'] = True
+ os_client_args['insecure'] = CGTS_INSECURE_SSL
os_client_args['os_auth_url'] = new_auth_url.geturl()
os_client_args['os_endpoint_type'] = 'public'
# os_client_args['system_url'] = os_client_args['os_auth_url']
def get_dc_access_info():
try:
- # client_args = dict(
- # auth_url=os.environ.get('OS_AUTH_URL', _DEFAULT_STX_URL),
- # username=os.environ.get('OS_USERNAME', "admin"),
- # api_key=os.environ.get('OS_PASSWORD', "fakepasswd1"),
- # project_name=os.environ.get('OS_PROJECT_NAME', "admin"),
- # )
client_args = get_stx_client_args()
except KeyError:
logger.error('Please source your RC file before execution, '
auth_url = urlparse(os_client_args.pop('os_auth_url'))
hostname = f"[{auth_url.hostname}]" if is_ipv6(auth_url.hostname) \
else auth_url.hostname
- dcmanager_url = urlparse(_DEFAULT_DCMANAGER_URL)
+ dcmanager_url = urlparse(get_dc_manager_url())
dcmanager_url = dcmanager_url._replace(netloc=dcmanager_url.netloc.replace(
dcmanager_url.hostname, hostname))
def get_fm_access_info(subcloud_hostname: str = "",
sub_is_https: bool = False):
try:
- # client_args = dict(
- # auth_url=os.environ.get('OS_AUTH_URL', _DEFAULT_STX_URL),
- # username=os.environ.get('OS_USERNAME', "admin"),
- # api_key=os.environ.get('OS_PASSWORD', "fakepasswd1"),
- # project_name=os.environ.get('OS_PROJECT_NAME', "admin"),
- # )
client_args = get_stx_client_args()
except KeyError:
logger.error('Please source your RC file before execution, '
if "" != subcloud_hostname:
subcloud_hostname = f"[{subcloud_hostname}]" if \
is_ipv6(subcloud_hostname) else subcloud_hostname
- orig_auth_url = urlparse(_DEFAULT_STX_URL)
+ orig_auth_url = urlparse(get_stx_url())
new_auth_url = orig_auth_url._replace(
netloc=orig_auth_url.netloc.replace(
orig_auth_url.hostname, subcloud_hostname))
os_client_args['auth_url'] = new_auth_url.geturl()
os_client_args['endpoint_type'] = 'publicURL'
- os_client_args['insecure'] = True
+ os_client_args['insecure'] = CGTS_INSECURE_SSL
os_client_args['username'] = os_client_args.pop('os_username')
os_client_args['password'] = os_client_args.pop('os_api_key')
def get_auth_provider():
- return 'k8s'
+ return config.conf.auth_provider
def get_dms_support_profiles():
-# Copyright (C) 2022 Wind River Systems, Inc.
+# Copyright (C) 2022-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
return (count, result)
def _update(self, alarm_event_record: alarm_obj.AlarmEventRecord):
- self.session.add(alarm_event_record)
+ self.session.merge(alarm_event_record)
def _delete(self, alarm_event_record_id):
self.session.query(alarm_obj.AlarmEventRecord).filter_by(
-# Copyright (C) 2022 Wind River Systems, Inc.
+# Copyright (C) 2022-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
def _set_stx_client(self):
self.driver.setFaultClient(self._pool_id)
+ def delete(self, id) -> alarmModel.FaultGenericModel:
+ return self.driver.deleteAlarm(id)
+
class StxEventClient(BaseClient):
- def __init__(self, driver=None):
+ def __init__(self, uow: unit_of_work.AbstractUnitOfWork, driver=None):
super().__init__()
self.driver = driver if driver else StxFaultClientImp()
+ self.uow = uow
def _get(self, id) -> alarmModel.FaultGenericModel:
return self.driver.getEventInfo(id)
def _set_stx_client(self):
self.driver.setFaultClient(self._pool_id)
+ def suppression_list(self, alarm_id) -> List[alarmModel.FaultGenericModel]:
+ return self.driver.getSuppressionList(alarm_id)
+
+ def suppress(self, id) -> alarmModel.FaultGenericModel:
+ return self.driver.suppressEvent(id)
+
# internal driver which implement client call to Stx Fault Management instance
class StxFaultClientImp(object):
try:
sub_is_https = False
os_client_args = config.get_stx_access_info(
- region_name=subcloud[0].name,
+ region_name=subcloud[0].region_name,
subcloud_hostname=subcloud[0].oam_floating_ip)
stx_client = get_stx_client(**os_client_args)
except EndpointException as e:
if CGTSCLIENT_ENDPOINT_ERROR_MSG in msg:
sub_is_https = True
os_client_args = config.get_stx_access_info(
- region_name=subcloud[0].name, sub_is_https=sub_is_https,
+ region_name=subcloud[0].region_name,
+ sub_is_https=sub_is_https,
subcloud_hostname=subcloud[0].oam_floating_ip)
stx_client = get_stx_client(**os_client_args)
else:
alarms = self.fmclient.alarm.list(expand=True)
if len(alarms) == 0:
return []
- logger.debug('alarm 1:' + str(alarms[0].to_dict()))
- # [print('alarm:' + str(alarm.to_dict())) for alarm in alarms if alarm]
+ [logger.debug(
+ 'alarm:' + str(alarm.to_dict())) for alarm in alarms if alarm]
return [alarmModel.FaultGenericModel(
alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
for alarm in alarms if alarm]
def getAlarmInfo(self, id) -> alarmModel.FaultGenericModel:
try:
alarm = self.fmclient.alarm.get(id)
- logger.debug('get alarm id ' + id + ':' + str(alarm.to_dict()))
+ logger.debug(
+ 'get alarm id: ' + id + ', result:' + str(alarm.to_dict()))
except HTTPNotFound:
event = self.fmclient.event_log.get(id)
return alarmModel.FaultGenericModel(
return alarmModel.FaultGenericModel(
alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
+ def deleteAlarm(self, id) -> alarmModel.FaultGenericModel:
+ alarm = self.fmclient.alarm.delete(id)
+ logger.debug('delete alarm id ' + id + ':' + str(alarm.to_dict()))
+ return alarmModel.FaultGenericModel(
+ alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
+
def getEventList(self, **filters) -> List[alarmModel.FaultGenericModel]:
events = self.fmclient.event_log.list(alarms=True, expand=True)
- logger.debug('event 1:' + str(events[0].to_dict()))
- # [print('alarm:' + str(event.to_dict())) for event in events if event]
+ [logger.debug(
+ 'alarm:' + str(event.to_dict())) for event in events if event]
return [alarmModel.FaultGenericModel(
alarmModel.EventTypeEnum.EVENT, self._eventconverter(event))
for event in events if event]
return alarmModel.FaultGenericModel(
alarmModel.EventTypeEnum.EVENT, self._eventconverter(event))
+ def suppressEvent(self, id) -> alarmModel.FaultGenericModel:
+ patch = [dict(path='/' + 'suppression_status', value='suppressed',
+ op='replace')]
+ event = self.fmclient.event_suppression.update(id, patch)
+ logger.debug('suppressed event id ' + id + ':' + str(event.to_dict()))
+ return alarmModel.FaultGenericModel(
+ alarmModel.EventTypeEnum.EVENT, self._suppression_converter(event))
+
+ def getSuppressionList(self, alarm_id) -> alarmModel.FaultGenericModel:
+ suppression_list = []
+ query_as_array = []
+ events = self.fmclient.event_suppression.list(q=query_as_array)
+ for event in events:
+ if event.alarm_id == alarm_id:
+ # logger.debug('suppression event:' + str(event.to_dict()))
+ suppression_list.append(
+ alarmModel.FaultGenericModel(
+ alarmModel.EventTypeEnum.EVENT,
+ self._suppression_converter(event)))
+ return suppression_list
+
@ staticmethod
def _alarmconverter(alarm):
selected_keys = [
uuid.NAMESPACE_URL, event.probable_cause)))
return event
+ @ staticmethod
+ def _suppression_converter(event, clear=False):
+ selected_keys = [
+ 'alarm_id', 'description', 'suppression_status',
+ 'links'
+ ]
+ content = event.to_dict()
+ filtered = dict(
+ filter(lambda item: item[0] in selected_keys, content.items()))
+ setattr(event, 'filtered', filtered)
+ setattr(event, 'uuid', event.uuid)
+ setattr(event, 'alarm_id', event.alarm_id)
+ setattr(event, 'description', event.description)
+ setattr(event, 'suppression_status', event.suppression_status)
+ setattr(event, 'alarm_type', None)
+ setattr(event, 'alarm_def_id', None)
+ setattr(event, 'probable_cause_id', None)
+ setattr(event, 'state', None)
+ setattr(event, 'timestamp', None)
+ return event
+
@ staticmethod
def _alarmeventhasher(event, state=''):
# The event model and the alarm model have different parameter name
subcloud_stxclient = self.getSubcloudClient(
subcloud.subcloud_id)
systems = subcloud_stxclient.isystem.list()
- logger.debug('systems:' + str(systems[0].to_dict()))
+ logger.debug('subcloud system:' + str(systems[0].to_dict()))
pools.append(systems[0])
except Exception as ex:
logger.warning('Failed get cgstclient of subcloud %s: %s' %
MANUAL = 'MANUAL'
+class AlarmEventRecordModifications(AgRoot):
+ def __init__(self, ack: bool = None,
+ clear: PerceivedSeverityEnum = None) -> None:
+ super().__init__()
+ self.alarmAcknowledged = ack
+ self.perceivedSeverity = clear
+
+
class AlarmDefinition(AgRoot, Serializer):
def __init__(self, id: str, name: str, change_type: AlarmChangeTypeEnum,
desc: str, prop_action: str, clearing_type: ClearingTypeEnum,
class AlarmEvent2SMO(Serializer):
def __init__(self, eventtype: AlarmNotificationEventEnum,
- id: str, ref: str, updatetime: str) -> None:
+ id: str, updatetime: str) -> None:
self.notificationEventType = eventtype
- self.objectRef = ref
self.id = id
self.updatetime = updatetime
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@dataclass
class UpdateAlarm(UpdateFaultObject):
parentid: str
+
+
+@dataclass
+class PurgeAlarmEvent(UpdateFaultObject):
+ data: AlarmEvent2SMO
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
id: str
notificationEventType: AlarmNotificationEventEnum
updatetime: datetime.now()
+
+
+@dataclass
+class AlarmEventPurged(Event):
+ id: str
+ notificationEventType: AlarmNotificationEventEnum
+ updatetime: datetime.now()
alarm_event_record = uow.alarm_event_records.get(fmobj.id)
if not alarm_event_record:
- logger.info("add alarm event record:" + fmobj.name
- + " update_at: " + str(fmobj.updatetime)
- + " id: " + str(fmobj.id)
- + " hash: " + str(fmobj.hash))
localmodel = create_by(fmobj)
content = json.loads(fmobj.content)
entity_type_id = content['entity_type_id']
extensions = json.loads(host.extensions)
if extensions['hostname'] == hostname:
localmodel.resourceId = host.resourceId
+ break
+ else:
+ # Example would be when alarm has host=controller
+ # TODO: Handle host=controller better
+ logger.warning(
+ 'Couldnt match alarm event '
+ f'to hostname for: {content}')
+ return
uow.alarm_event_records.add(localmodel)
logger.info("Add the alarm event record: " + fmobj.id
+ ", name: " + fmobj.name)
--- /dev/null
+# Copyright (C) 2024 Wind River Systems, Inc.
+#
+# 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.
+
+import json
+
+from o2common.adapter.notifications import AbstractNotifications
+from o2common.config import conf
+from o2common.domain.filter import gen_orm_filter
+from o2common.helper import o2logging
+from o2common.service.unit_of_work import AbstractUnitOfWork
+from o2ims.adapter.clients.fault_client import StxAlarmClient
+from o2ims.domain import commands, alarm_obj
+logger = o2logging.get_logger(__name__)
+
+
+def purge_alarm_event(
+ cmd: commands.PubAlarm2SMO,
+ uow: AbstractUnitOfWork,
+ notifications: AbstractNotifications,
+):
+ """
+ Purges an alarm event and notifies relevant subscribers.
+
+ This method performs the following steps:
+ 1. Retrieves data from the command object and initializes the fault client.
+ 2. Uses the Unit of Work pattern to find and delete the corresponding
+ alarm event record.
+ 3. Updates the alarm event record's hash, extensions, changed time,
+ and perceived severity.
+ 4. Commits the changes to the database.
+ 5. Finds and processes all alarm subscriptions, deciding whether to
+ send notifications based on subscription filters.
+
+ Parameters:
+ - cmd (commands.PubAlarm2SMO): Command object containing the alarm
+ event data.
+ - uow (AbstractUnitOfWork): Unit of Work object for managing
+ database transactions.
+ - notifications (AbstractNotifications): Abstract notifications
+ object for sending notifications.
+
+ Exceptions:
+ - Any exceptions that might occur during database operations or
+ notification sending.
+ """
+ fault_client = StxAlarmClient(uow)
+ data = cmd.data
+ with uow:
+ alarm_event_record = uow.alarm_event_records.get(data.id)
+ alarm = fault_client.delete(alarm_event_record.alarmEventRecordId)
+ alarm_event_record.hash = alarm.hash
+ alarm_event_record.extensions = json.dumps(alarm.filtered)
+ alarm_event_record.alarmChangedTime = alarm.updatetime.\
+ strftime("%Y-%m-%dT%H:%M:%S")
+ alarm_event_record.perceivedSeverity = \
+ alarm_obj.PerceivedSeverityEnum.CLEARED
+
+ uow.alarm_event_records.update(alarm_event_record)
+
+ uow.commit()
+
+ alarm = uow.alarm_event_records.get(data.id)
+ subs = uow.alarm_subscriptions.list()
+ for sub in subs:
+ sub_data = sub.serialize()
+ logger.debug('Alarm Subscription: {}'.format(
+ sub_data['alarmSubscriptionId']))
+
+ if not sub_data.get('filter', None):
+ callback_smo(notifications, sub, data, alarm)
+ continue
+ try:
+ args = gen_orm_filter(alarm_obj.AlarmEventRecord,
+ sub_data['filter'])
+ except KeyError:
+ logger.warning(
+ 'Alarm Subscription {} filter {} has wrong attribute '
+ 'name or value. Ignore the filter'.format(
+ sub_data['alarmSubscriptionId'],
+ sub_data['filter']))
+ callback_smo(notifications, sub, data, alarm)
+ continue
+ args.append(alarm_obj.AlarmEventRecord.
+ alarmEventRecordId == data.id)
+ count, _ = uow.alarm_event_records.list_with_count(*args)
+ if count != 0:
+ logger.debug(
+ 'Alarm Event {} skip for subscription {} because of '
+ 'the filter.'
+ .format(data.id, sub_data['alarmSubscriptionId']))
+ continue
+ callback_smo(notifications, sub, data, alarm)
+
+
+def callback_smo(notifications: AbstractNotifications,
+ sub: alarm_obj.AlarmSubscription,
+ msg: alarm_obj.AlarmEvent2SMO,
+ alarm: alarm_obj.AlarmEventRecord):
+ sub_data = sub.serialize()
+ alarm_data = alarm.serialize()
+ callback = {
+ 'globalCloudID': conf.DEFAULT.ocloud_global_id,
+ 'consumerSubscriptionId': sub_data['consumerSubscriptionId'],
+ 'notificationEventType': msg.notificationEventType,
+ 'objectRef': msg.objectRef,
+ 'alarmEventRecordId': alarm_data['alarmEventRecordId'],
+ 'resourceTypeID': alarm_data['resourceTypeId'],
+ 'resourceID': alarm_data['resourceId'],
+ 'alarmDefinitionID': alarm_data['alarmDefinitionId'],
+ 'probableCauseID': alarm_data['probableCauseId'],
+ 'alarmRaisedTime': alarm_data['alarmRaisedTime'],
+ 'alarmChangedTime': alarm_data['alarmChangedTime'],
+ 'alarmAcknowledgeTime': alarm_data['alarmAcknowledgeTime'],
+ 'alarmAcknowledged': alarm_data['alarmAcknowledged'],
+ 'perceivedSeverity': alarm_data['perceivedSeverity'],
+ 'extensions': json.loads(alarm_data['extensions'])
+ }
+ logger.info('callback URL: {}'.format(sub_data['callback']))
+ logger.debug('callback data: {}'.format(json.dumps(callback)))
+
+ return notifications.send(sub_data['callback'], callback)
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
event: events.AlarmEventChanged,
publish: Callable,
):
- logger.debug('In notify_alarm_event_change')
publish("AlarmEventChanged", event)
logger.debug("published Alarm Event Changed: {}".format(
event.id))
+
+
+def notify_alarm_event_purge(
+ event: events.AlarmEventPurged,
+ publish: Callable,
+):
+ publish("AlarmEventPurged", event)
+ logger.debug("published Alarm Event Purged: {}".format(
+ event.id))
# 'alarmAcknowledgeTime,alarmAcknowledged,extensions}'
)
+ alarm_event_record_patch = api_monitoring_v1.model(
+ "AlarmPatchDto",
+ {
+ 'alarmAcknowledged': fields.Boolean(
+ example=True,
+ description='Boolean value indication of a management ' +
+ 'system has acknowledged the alarm.'),
+ 'perceivedSeverity': fields.String(
+ example='5',
+ description='indicate that the alarm record is requested ' +
+ 'to be cleared. Only the value "5" for "CLEARED" is ' +
+ 'permitted in a request message content. ')
+ },
+ mask='{alarmAcknowledged,}'
+ )
+
class SubscriptionDTO:
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
from o2common.views.pagination_route import link_header, PAGE_PARAM
from o2common.views.route_exception import NotFoundException, \
BadRequestException
+from o2ims.domain.alarm_obj import PerceivedSeverityEnum
from o2ims.views import alarm_view
from o2ims.views.api_ns import api_ims_monitoring as api_monitoring_v1
from o2ims.views.alarm_dto import AlarmDTO, SubscriptionDTO, \
class AlarmGetRouter(Resource):
model = AlarmDTO.alarm_event_record_get
+ patch = AlarmDTO.alarm_event_record_patch
@api_monitoring_v1.doc('Get Alarm Event Record Information')
@api_monitoring_v1.marshal_with(model)
raise NotFoundException(
"Alarm Event Record {} doesn't exist".format(alarmEventRecordId))
+ @api_monitoring_v1.doc('Patch Alarm Event Record Information')
+ @api_monitoring_v1.expect(patch)
+ @api_monitoring_v1.marshal_with(patch)
+ def patch(self, alarmEventRecordId):
+ data = api_monitoring_v1.payload
+ ack_action = data.get('alarmAcknowledged', None)
+ clear_action = data.get('perceivedSeverity', None)
+
+ ack_is_none = ack_action is None
+ clear_is_none = clear_action is None
+ if (ack_is_none and clear_is_none) or (not ack_is_none and
+ not clear_is_none):
+ raise BadRequestException('Either "alarmAcknowledged" or '
+ '"perceivedSeverity" shall be included '
+ 'in a request, but not both.')
+ if ack_action:
+ result = alarm_view.alarm_event_record_ack(alarmEventRecordId,
+ bus.uow)
+ if result is not None:
+ return result
+ elif clear_action:
+ if clear_action != PerceivedSeverityEnum.CLEARED:
+ raise BadRequestException(
+ 'Only the value "5" for "CLEARED" is permitted of '
+ '"perceivedSeverity".')
+
+ result = alarm_view.alarm_event_record_clear(alarmEventRecordId,
+ bus.uow)
+ if result is not None:
+ return result
+ raise NotFoundException(
+ "Alarm Event Record {} doesn't exist".format(alarmEventRecordId))
+
# ---------- Alarm Subscriptions ---------- #
@api_monitoring_v1.route("/v1/alarmSubscriptions")
-# Copyright (C) 2021 Wind River Systems, Inc.
+# Copyright (C) 2021-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# See the License for the specific language governing permissions and
# limitations under the License.
+from datetime import datetime
import uuid as uuid
-from o2common.service import unit_of_work
+from o2common.service import unit_of_work, messagebus
from o2common.views.view import gen_filter, check_filter
from o2common.views.pagination_view import Pagination
from o2common.views.route_exception import BadRequestException, \
NotFoundException
+from o2ims.domain import events
from o2ims.views.alarm_dto import SubscriptionDTO
-from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEventRecord
+from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEventRecord, \
+ AlarmNotificationEventEnum, AlarmEventRecordModifications, \
+ PerceivedSeverityEnum
from o2common.helper import o2logging
# from o2common.config import config
return first.serialize() if first is not None else None
+def alarm_event_record_ack(alarmEventRecordId: str,
+ uow: unit_of_work.AbstractUnitOfWork):
+ with uow:
+ alarm_event_record = uow.alarm_event_records.get(alarmEventRecordId)
+ # Check the record does not exist, return None. Otherwise, the
+ # acknowledge request will update the record even if it is
+ # acknowledged.
+ if alarm_event_record is None:
+ return None
+ alarm_event_record.alarmAcknowledged = True
+ alarm_event_record.alarmAcknowledgeTime = datetime.\
+ now().strftime("%Y-%m-%dT%H:%M:%S")
+ uow.alarm_event_records.update(alarm_event_record)
+ uow.commit()
+
+ result = AlarmEventRecordModifications(True)
+ return result
+
+
+def alarm_event_record_clear(alarmEventRecordId: str,
+ uow: unit_of_work.AbstractUnitOfWork):
+ with uow:
+ alarm_event_record = uow.alarm_event_records.get(alarmEventRecordId)
+ if alarm_event_record is None:
+ return None
+ elif alarm_event_record.perceivedSeverity == \
+ PerceivedSeverityEnum.CLEARED:
+ raise BadRequestException(
+ "Alarm Event Record {} has already been marked as CLEARED."
+ .format(alarmEventRecordId))
+ alarm_event_record.events.append(events.AlarmEventPurged(
+ id=alarm_event_record.alarmEventRecordId,
+ notificationEventType=AlarmNotificationEventEnum.CLEAR,
+ updatetime=alarm_event_record.alarmAcknowledgeTime))
+
+ uow.alarm_event_records.update(alarm_event_record)
+ uow.commit()
+
+ result = AlarmEventRecordModifications(
+ clear=PerceivedSeverityEnum.CLEARED)
+ _handle_events(messagebus.MessageBus.get_instance())
+ return result
+
+
+def _handle_events(bus: messagebus.MessageBus):
+ # handle events
+ events = bus.uow.collect_new_events()
+ for event in events:
+ bus.handle(event)
+ return True
+
+
def subscriptions(uow: unit_of_work.AbstractUnitOfWork, **kwargs):
pagination = Pagination(**kwargs)
query_kwargs = pagination.get_pagination()
# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client
# -e git+https://opendev.org/starlingx/fault.git@master#egg=fmclient&subdirectory=python-fmclient/fmclient
--e git+https://opendev.org/starlingx/distcloud-client.git@eb4e7eeeb09bdf2e1b80984b378c5a8ea9930f04#egg=distributedcloud-client&subdirectory=distributedcloud-client
--e git+https://opendev.org/starlingx/config.git@r/stx.7.0#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client
--e git+https://opendev.org/starlingx/fault.git@r/stx.7.0#egg=fmclient&subdirectory=python-fmclient/fmclient
+-e git+https://opendev.org/starlingx/distcloud-client.git@b4a8ec19dc6078952a3762d7eee8d426d520a1f0#egg=distributedcloud-client&subdirectory=distributedcloud-client
+# Updated to lastest commit at May 24th 2024
+-e git+https://opendev.org/starlingx/config.git@04271215320b8ab9824be837ac5aae07883d363b#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client
+-e git+https://opendev.org/starlingx/fault.git@b213c9155b2323831143977447f41efc2f84b76a#egg=fmclient&subdirectory=python-fmclient/fmclient
\ No newline at end of file
pyOpenSSL
gunicorn
+
+# Import JWT to support OAuth2
+pyjwt==2.6.0
-# Copyright (C) 2022 Wind River Systems, Inc.
+# Copyright (C) 2022-2024 Wind River Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# pylint: disable=redefined-outer-name
import shutil
import subprocess
+import sys
import time
from pathlib import Path
from unittest.mock import MagicMock
from mock_alchemy.mocking import UnifiedAlchemyMagicMock
+# Mock cgtsclient, dcmanagerclient, fmclient
+modules_to_mock = [
+ 'cgtsclient',
+ 'cgtsclient.client',
+ 'cgtsclient.exc',
+ 'dcmanagerclient',
+ 'dcmanagerclient.api',
+ 'dcmanagerclient.api.client',
+ 'fmclient',
+ 'fmclient.client',
+ 'fmclient.common',
+ 'fmclient.common.exceptions'
+]
+
+for module_name in modules_to_mock:
+ sys.modules[module_name] = MagicMock()
+
from o2app.bootstrap import bootstrap
from o2ims.views import configure_namespace
from o2app.adapter import unit_of_work