efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 1 | # ============LICENSE_START=================================================== |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 2 | # Copyright (C) 2019-2020 Nordix Foundation. |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 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===================================================== |
ERIMROB | b074a92 | 2020-02-27 10:05:37 +0000 | [diff] [blame] | 18 | |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 19 | from enum import Enum |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 20 | |
ERIMROB | b074a92 | 2020-02-27 10:05:37 +0000 | [diff] [blame] | 21 | from tenacity import retry, retry_if_exception_type, wait_exponential, stop_after_attempt |
| 22 | |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 23 | import mod.pmsh_logging as logger |
| 24 | from mod import db |
ERIMROB | e97cde0 | 2020-04-01 17:54:19 +0100 | [diff] [blame^] | 25 | from mod.db_models import SubscriptionModel, NfSubRelationalModel, NetworkFunctionModel |
| 26 | from mod.network_function import NetworkFunction |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 27 | |
| 28 | |
| 29 | class SubNfState(Enum): |
| 30 | PENDING_CREATE = 'PENDING_CREATE' |
| 31 | CREATE_FAILED = 'CREATE_FAILED' |
| 32 | CREATED = 'CREATED' |
| 33 | PENDING_DELETE = 'PENDING_DELETE' |
| 34 | DELETE_FAILED = 'DELETE_FAILED' |
| 35 | |
| 36 | |
| 37 | class AdministrativeState(Enum): |
| 38 | UNLOCKED = 'UNLOCKED' |
| 39 | LOCKED = 'LOCKED' |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 40 | |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 41 | |
ERIMROB | b074a92 | 2020-02-27 10:05:37 +0000 | [diff] [blame] | 42 | subscription_nf_states = { |
| 43 | AdministrativeState.LOCKED.value: { |
| 44 | 'success': SubNfState.CREATED, |
| 45 | 'failed': SubNfState.DELETE_FAILED |
| 46 | }, |
| 47 | AdministrativeState.UNLOCKED.value: { |
| 48 | 'success': SubNfState.CREATED, |
| 49 | 'failed': SubNfState.CREATE_FAILED |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 54 | class Subscription: |
| 55 | def __init__(self, **kwargs): |
| 56 | self.subscriptionName = kwargs.get('subscriptionName') |
| 57 | self.administrativeState = kwargs.get('administrativeState') |
| 58 | self.fileBasedGP = kwargs.get('fileBasedGP') |
| 59 | self.fileLocation = kwargs.get('fileLocation') |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 60 | self.nfFilter = kwargs.get('nfFilter') |
| 61 | self.measurementGroups = kwargs.get('measurementGroups') |
| 62 | |
emartin | c19a0a8 | 2020-02-27 13:56:52 +0000 | [diff] [blame] | 63 | def prepare_subscription_event(self, xnf_name, app_conf): |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 64 | """Prepare the sub event for publishing |
| 65 | |
| 66 | Args: |
| 67 | xnf_name: the AAI xnf name. |
emartin | c19a0a8 | 2020-02-27 13:56:52 +0000 | [diff] [blame] | 68 | app_conf (AppConfig): the application configuration. |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 69 | |
| 70 | Returns: |
| 71 | dict: the Subscription event to be published. |
| 72 | """ |
| 73 | clean_sub = {k: v for k, v in self.__dict__.items() if k != 'nfFilter'} |
emartin | c19a0a8 | 2020-02-27 13:56:52 +0000 | [diff] [blame] | 74 | sub_event = {'nfName': xnf_name, 'policyName': app_conf.operational_policy_name, |
| 75 | 'changeType': 'DELETE' |
| 76 | if self.administrativeState == AdministrativeState.LOCKED.value |
| 77 | else 'CREATE', 'closedLoopControlName': app_conf.control_loop_name, |
| 78 | 'subscription': clean_sub} |
| 79 | return sub_event |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 80 | |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 81 | def create(self): |
| 82 | """ Creates a subscription database entry |
efiacor | bbe05d8 | 2019-12-11 12:00:26 +0000 | [diff] [blame] | 83 | |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 84 | Returns: |
| 85 | Subscription object |
| 86 | """ |
| 87 | existing_subscription = (SubscriptionModel.query.filter( |
| 88 | SubscriptionModel.subscription_name == self.subscriptionName).one_or_none()) |
| 89 | |
| 90 | if existing_subscription is None: |
| 91 | new_subscription = SubscriptionModel(subscription_name=self.subscriptionName, |
| 92 | status=self.administrativeState) |
| 93 | |
| 94 | db.session.add(new_subscription) |
| 95 | db.session.commit() |
| 96 | |
| 97 | return new_subscription |
| 98 | |
| 99 | else: |
| 100 | logger.debug(f'Subscription {self.subscriptionName} already exists,' |
| 101 | f' returning this subscription..') |
| 102 | return existing_subscription |
| 103 | |
| 104 | def add_network_functions_to_subscription(self, nf_list): |
| 105 | """ Associates network functions to a Subscription |
| 106 | |
| 107 | Args: |
| 108 | nf_list : A list of NetworkFunction objects. |
| 109 | """ |
| 110 | current_sub = self.create() |
| 111 | logger.debug(f'Adding network functions to subscription {current_sub.subscription_name}') |
| 112 | |
| 113 | for nf in nf_list: |
| 114 | current_nf = nf.create() |
| 115 | |
| 116 | existing_entry = NfSubRelationalModel.query.filter( |
| 117 | NfSubRelationalModel.subscription_name == current_sub.subscription_name, |
| 118 | NfSubRelationalModel.nf_name == current_nf.nf_name).one_or_none() |
| 119 | if existing_entry is None: |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 120 | new_nf_sub = NfSubRelationalModel(current_sub.subscription_name, |
| 121 | nf.nf_name, SubNfState.PENDING_CREATE.value) |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 122 | new_nf_sub.nf = current_nf |
| 123 | logger.debug(current_nf) |
| 124 | current_sub.nfs.append(new_nf_sub) |
| 125 | |
| 126 | db.session.add(current_sub) |
| 127 | db.session.commit() |
| 128 | |
| 129 | @staticmethod |
| 130 | def get(subscription_name): |
| 131 | """ Retrieves a subscription |
| 132 | |
| 133 | Args: |
| 134 | subscription_name (str): The subscription name |
| 135 | |
| 136 | Returns: |
| 137 | Subscription object else None |
| 138 | """ |
| 139 | return SubscriptionModel.query.filter( |
| 140 | SubscriptionModel.subscription_name == subscription_name).one_or_none() |
| 141 | |
| 142 | @staticmethod |
| 143 | def get_all(): |
| 144 | """ Retrieves a list of subscriptions |
| 145 | |
| 146 | Returns: |
| 147 | list: Subscription list else empty |
| 148 | """ |
| 149 | return SubscriptionModel.query.all() |
| 150 | |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 151 | def update_subscription_status(self): |
| 152 | """ Updates the status of subscription in subscription table """ |
| 153 | SubscriptionModel.query.filter( |
| 154 | SubscriptionModel.subscription_name == self.subscriptionName). \ |
| 155 | update({SubscriptionModel.status: self.administrativeState}, |
| 156 | synchronize_session='evaluate') |
| 157 | |
| 158 | db.session.commit() |
| 159 | |
| 160 | def delete_subscription(self): |
emartin | e7f6914 | 2020-02-24 14:13:03 +0000 | [diff] [blame] | 161 | """ Deletes a subscription and all its association from the database. A network function |
| 162 | that is only associated with the subscription being removed will also be deleted.""" |
| 163 | subscription = SubscriptionModel.query.filter( |
| 164 | SubscriptionModel.subscription_name == self.subscriptionName).one_or_none() |
| 165 | if subscription: |
| 166 | for nf_relationship in subscription.nfs: |
| 167 | other_nf_relationship = NfSubRelationalModel.query.filter( |
| 168 | NfSubRelationalModel.subscription_name != self.subscriptionName, |
| 169 | NfSubRelationalModel.nf_name == nf_relationship.nf_name).one_or_none() |
| 170 | if not other_nf_relationship: |
| 171 | db.session.delete(nf_relationship.nf) |
| 172 | db.session.delete(subscription) |
| 173 | db.session.commit() |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 174 | |
| 175 | @retry(wait=wait_exponential(multiplier=1, min=30, max=120), stop=stop_after_attempt(3), |
| 176 | retry=retry_if_exception_type(Exception)) |
emartin | c19a0a8 | 2020-02-27 13:56:52 +0000 | [diff] [blame] | 177 | def process_subscription(self, nfs, mr_pub, app_conf): |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 178 | action = 'Deactivate' |
| 179 | sub_nf_state = SubNfState.PENDING_DELETE.value |
| 180 | self.update_subscription_status() |
| 181 | |
| 182 | if self.administrativeState == AdministrativeState.UNLOCKED.value: |
| 183 | action = 'Activate' |
| 184 | sub_nf_state = SubNfState.PENDING_CREATE.value |
| 185 | |
| 186 | try: |
| 187 | for nf in nfs: |
emartin | c19a0a8 | 2020-02-27 13:56:52 +0000 | [diff] [blame] | 188 | mr_pub.publish_subscription_event_data(self, nf.nf_name, app_conf) |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 189 | logger.debug(f'Publishing Event to {action} ' |
| 190 | f'Sub: {self.subscriptionName} for the nf: {nf.nf_name}') |
| 191 | self.add_network_functions_to_subscription(nfs) |
| 192 | self.update_sub_nf_status(self.subscriptionName, sub_nf_state, nf.nf_name) |
| 193 | except Exception as err: |
| 194 | raise Exception(f'Error publishing activation event to MR: {err}') |
| 195 | |
efiacor | 8b3fc62 | 2020-01-24 13:19:01 +0000 | [diff] [blame] | 196 | @staticmethod |
| 197 | def get_all_nfs_subscription_relations(): |
| 198 | """ Retrieves all network function to subscription relations |
| 199 | |
| 200 | Returns: |
| 201 | list: NetworkFunctions per Subscription list else empty |
| 202 | """ |
| 203 | nf_per_subscriptions = NfSubRelationalModel.query.all() |
| 204 | |
| 205 | return nf_per_subscriptions |
| 206 | |
ERIMROB | 26b76c0 | 2020-02-12 11:35:20 +0000 | [diff] [blame] | 207 | @staticmethod |
| 208 | def update_sub_nf_status(subscription_name, status, nf_name): |
| 209 | """ Updates the status of the subscription for a particular nf |
| 210 | |
| 211 | Args: |
| 212 | subscription_name (str): The subscription name |
| 213 | nf_name (str): The network function name |
| 214 | status (str): Status of the subscription |
| 215 | """ |
| 216 | NfSubRelationalModel.query.filter( |
| 217 | NfSubRelationalModel.subscription_name == subscription_name, |
| 218 | NfSubRelationalModel.nf_name == nf_name). \ |
| 219 | update({NfSubRelationalModel.nf_sub_status: status}, synchronize_session='evaluate') |
| 220 | |
| 221 | db.session.commit() |
ERIMROB | e97cde0 | 2020-04-01 17:54:19 +0100 | [diff] [blame^] | 222 | |
| 223 | def _get_nf_models(self): |
| 224 | nf_sub_relationships = NfSubRelationalModel.query.filter( |
| 225 | NfSubRelationalModel.subscription_name == self.subscriptionName) |
| 226 | nf_models = [] |
| 227 | for nf_sub_entry in nf_sub_relationships: |
| 228 | nf_model_object = NetworkFunctionModel.query.filter( |
| 229 | NetworkFunctionModel.nf_name == nf_sub_entry.nf_name).one_or_none() |
| 230 | nf_models.append(nf_model_object) |
| 231 | |
| 232 | return nf_models |
| 233 | |
| 234 | def get_network_functions(self): |
| 235 | nfs = [] |
| 236 | nf_models = self._get_nf_models() |
| 237 | for nf_model in nf_models: |
| 238 | nf = NetworkFunction( |
| 239 | nf_name=nf_model.nf_name, |
| 240 | orchestration_status=nf_model.orchestration_status |
| 241 | ) |
| 242 | nfs.append(nf) |
| 243 | |
| 244 | return nfs |