Add Support for Activation and Deactivation
[dcaegen2/services.git] / components / pm-subscription-handler / pmsh_service / mod / subscription.py
1 # ============LICENSE_START===================================================
2 #  Copyright (C) 2019-2020 Nordix Foundation.
3 # ============================================================================
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #      http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16 # SPDX-License-Identifier: Apache-2.0
17 # ============LICENSE_END=====================================================
18 import re
19 from enum import Enum
20
21 import mod.pmsh_logging as logger
22 from mod import db
23 from mod.db_models import SubscriptionModel, NfSubRelationalModel
24 from tenacity import retry, retry_if_exception_type, wait_exponential, stop_after_attempt
25
26
27 class SubNfState(Enum):
28     PENDING_CREATE = 'PENDING_CREATE'
29     CREATE_FAILED = 'CREATE_FAILED'
30     CREATED = 'CREATED'
31     PENDING_DELETE = 'PENDING_DELETE'
32     DELETE_FAILED = 'DELETE_FAILED'
33
34
35 class AdministrativeState(Enum):
36     UNLOCKED = 'UNLOCKED'
37     LOCKED = 'LOCKED'
38
39
40 class Subscription:
41     def __init__(self, **kwargs):
42         self.subscriptionName = kwargs.get('subscriptionName')
43         self.administrativeState = kwargs.get('administrativeState')
44         self.fileBasedGP = kwargs.get('fileBasedGP')
45         self.fileLocation = kwargs.get('fileLocation')
46         self.nfTypeModelInvariantId = kwargs.get('nfTypeModelInvariantId')
47         self.nfFilter = kwargs.get('nfFilter')
48         self.measurementGroups = kwargs.get('measurementGroups')
49
50     def prepare_subscription_event(self, xnf_name):
51         """Prepare the sub event for publishing
52
53         Args:
54             xnf_name: the AAI xnf name.
55
56         Returns:
57             dict: the Subscription event to be published.
58         """
59         clean_sub = {k: v for k, v in self.__dict__.items() if k != 'nfFilter'}
60         clean_sub.update({'nfName': xnf_name, 'policyName': f'OP-{self.subscriptionName}',
61                           'changeType': 'DELETE'
62                           if self.administrativeState == AdministrativeState.LOCKED.value
63                           else 'CREATE'})
64         return clean_sub
65
66     def create(self):
67         """ Creates a subscription database entry
68
69         Returns:
70             Subscription object
71         """
72         existing_subscription = (SubscriptionModel.query.filter(
73             SubscriptionModel.subscription_name == self.subscriptionName).one_or_none())
74
75         if existing_subscription is None:
76             new_subscription = SubscriptionModel(subscription_name=self.subscriptionName,
77                                                  status=self.administrativeState)
78
79             db.session.add(new_subscription)
80             db.session.commit()
81
82             return new_subscription
83
84         else:
85             logger.debug(f'Subscription {self.subscriptionName} already exists,'
86                          f' returning this subscription..')
87             return existing_subscription
88
89     def add_network_functions_to_subscription(self, nf_list):
90         """ Associates network functions to a Subscription
91
92         Args:
93             nf_list : A list of NetworkFunction objects.
94         """
95         current_sub = self.create()
96         logger.debug(f'Adding network functions to subscription {current_sub.subscription_name}')
97
98         for nf in nf_list:
99             current_nf = nf.create()
100
101             existing_entry = NfSubRelationalModel.query.filter(
102                 NfSubRelationalModel.subscription_name == current_sub.subscription_name,
103                 NfSubRelationalModel.nf_name == current_nf.nf_name).one_or_none()
104             if existing_entry is None:
105                 new_nf_sub = NfSubRelationalModel(current_sub.subscription_name,
106                                                   nf.nf_name, SubNfState.PENDING_CREATE.value)
107                 new_nf_sub.nf = current_nf
108                 logger.debug(current_nf)
109                 current_sub.nfs.append(new_nf_sub)
110
111         db.session.add(current_sub)
112         db.session.commit()
113
114     @staticmethod
115     def get(subscription_name):
116         """ Retrieves a subscription
117
118         Args:
119             subscription_name (str): The subscription name
120
121         Returns:
122             Subscription object else None
123         """
124         return SubscriptionModel.query.filter(
125             SubscriptionModel.subscription_name == subscription_name).one_or_none()
126
127     @staticmethod
128     def get_all():
129         """ Retrieves a list of subscriptions
130
131         Returns:
132             list: Subscription list else empty
133         """
134         return SubscriptionModel.query.all()
135
136     def update_subscription_status(self):
137         """ Updates the status of subscription in subscription table """
138         SubscriptionModel.query.filter(
139             SubscriptionModel.subscription_name == self.subscriptionName). \
140             update({SubscriptionModel.status: self.administrativeState},
141                    synchronize_session='evaluate')
142
143         db.session.commit()
144
145     def delete_subscription(self):
146         """ Deletes a subscription from the database """
147         SubscriptionModel.query.filter(
148             SubscriptionModel.subscription_name == self.subscriptionName). \
149             delete(synchronize_session='evaluate')
150
151         db.session.commit()
152
153     @retry(wait=wait_exponential(multiplier=1, min=30, max=120), stop=stop_after_attempt(3),
154            retry=retry_if_exception_type(Exception))
155     def process_subscription(self, nfs, mr_pub):
156         action = 'Deactivate'
157         sub_nf_state = SubNfState.PENDING_DELETE.value
158         self.update_subscription_status()
159
160         if self.administrativeState == AdministrativeState.UNLOCKED.value:
161             action = 'Activate'
162             sub_nf_state = SubNfState.PENDING_CREATE.value
163
164         try:
165             for nf in nfs:
166                 mr_pub.publish_subscription_event_data(self, nf.nf_name)
167                 logger.debug(f'Publishing Event to {action} '
168                              f'Sub: {self.subscriptionName} for the nf: {nf.nf_name}')
169                 self.add_network_functions_to_subscription(nfs)
170                 self.update_sub_nf_status(self.subscriptionName, sub_nf_state, nf.nf_name)
171         except Exception as err:
172             raise Exception(f'Error publishing activation event to MR: {err}')
173
174     @staticmethod
175     def get_all_nfs_subscription_relations():
176         """ Retrieves all network function to subscription relations
177
178         Returns:
179             list: NetworkFunctions per Subscription list else empty
180         """
181         nf_per_subscriptions = NfSubRelationalModel.query.all()
182
183         return nf_per_subscriptions
184
185     @staticmethod
186     def update_sub_nf_status(subscription_name, status, nf_name):
187         """ Updates the status of the subscription for a particular nf
188
189         Args:
190             subscription_name (str): The subscription name
191             nf_name (str): The network function name
192             status (str): Status of the subscription
193         """
194         NfSubRelationalModel.query.filter(
195             NfSubRelationalModel.subscription_name == subscription_name,
196             NfSubRelationalModel.nf_name == nf_name). \
197             update({NfSubRelationalModel.nf_sub_status: status}, synchronize_session='evaluate')
198
199         db.session.commit()
200
201
202 class NetworkFunctionFilter:
203     def __init__(self, **kwargs):
204         self.nf_sw_version = kwargs.get('swVersions')
205         self.nf_names = kwargs.get('nfNames')
206         self.regex_matcher = re.compile('|'.join(raw_regex for raw_regex in self.nf_names))
207
208     def is_nf_in_filter(self, nf_name):
209         """Match the nf name against regex values in Subscription.nfFilter.nfNames
210
211         Args:
212             nf_name: the AAI nf name.
213
214         Returns:
215             bool: True if matched, else False.
216         """
217         return self.regex_matcher.search(nf_name)