<filename>catalog/packages/biz/vnf_pkg_subscription.py
# Copyright (C) 2019 Verizon. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import ast
import json
import logging
import os
import uuid
from collections import Counter

import requests
from requests.auth import HTTPBasicAuth
from rest_framework import status

from catalog.packages import const
from catalog.pub.database.models import VnfPkgSubscriptionModel
from catalog.pub.exceptions import VnfPkgSubscriptionException, \
    VnfPkgDuplicateSubscriptionException, SubscriptionDoesNotExistsException
from catalog.pub.utils.values import ignore_case_get

logger = logging.getLogger(__name__)

ROOT_FILTERS = {
    "notificationTypes": "notification_types",
    "vnfdId": "vnfd_id",
    "vnfPkgId": "vnf_pkg_id",
    "operationalState": "operation_states",
    "usageState": "usage_states"
}


def is_filter_type_equal(new_filter, existing_filter):
    return Counter(new_filter) == Counter(existing_filter)


class CreateSubscription(object):
    """
    Create subscription info
    """
    def __init__(self, data):
        self.data = data
        self.filter = ignore_case_get(self.data, "filter", {})
        self.callback_uri = ignore_case_get(self.data, "callbackUri")
        self.authentication = ignore_case_get(self.data, "authentication", {})
        self.notification_types = ignore_case_get(self.filter, "notificationTypes", [])
        self.operation_states = ignore_case_get(self.filter, "operationalState", [])
        self.usage_states = ignore_case_get(self.filter, "usageState", [])
        self.vnfd_id = ignore_case_get(self.filter, "vnfdId", [])
        self.vnf_pkg_id = ignore_case_get(self.filter, "vnfPkgId", [])
        self.vnf_products_from_provider = \
            ignore_case_get(self.filter, "vnfProductsFromProviders", [])

    def check_callbackuri_connection(self):
        """
        Check if the callback uri can access
        :return:
        """
        logger.debug("SubscribeNotification-post::> Sending GET request "
                     "to %s" % self.callback_uri)
        try:
            if self.authentication:
                if const.BASIC in self.authentication.get("authType", ''):
                    params = self.authentication.get("paramsBasic", {})
                    username = params.get("userName")
                    password = <PASSWORD>("password")
                    response = requests.get(self.callback_uri, auth=HTTPBasicAuth(username, password), timeout=2,
                                            verify=False)
                elif const.OAUTH2_CLIENT_CREDENTIALS in self.authentication.get("authType", ''):
                    # todo
                    pass
                else:
                    # todo
                    pass
            else:
                response = requests.get(self.callback_uri, timeout=2, verify=False)
            if response.status_code != status.HTTP_204_NO_CONTENT:
                raise VnfPkgSubscriptionException(
                    "callbackUri %s returns %s status code." % (
                        self.callback_uri,
                        response.status_code
                    )
                )
        except Exception:
            raise VnfPkgSubscriptionException(
                "callbackUri %s didn't return 204 status code." % self.callback_uri
            )

    def do_biz(self):
        """
        Do business
        :return:
        """
        self.subscription_id = str(uuid.uuid4())
        self.check_valid_auth_info()
        self.check_callbackuri_connection()
        self.check_valid()
        self.save_db()
        subscription = VnfPkgSubscriptionModel.objects.get(
            subscription_id=self.subscription_id
        )
        if subscription:
            return subscription.toDict()

    def check_valid_auth_info(self):
        """
        Check if the Auth info is valid
        :return:
        """
        logger.debug("SubscribeNotification--post::> Validating Auth "
                     "details if provided")
        if self.authentication.get("paramsBasic", {}) and const.BASIC not in self.authentication.get("authType"):
            raise VnfPkgSubscriptionException('Auth type should be ' + const.BASIC)
        if self.authentication.get("paramsOauth2ClientCredentials", {}) \
                and const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"):
            raise VnfPkgSubscriptionException('Auth type should be ' + const.OAUTH2_CLIENT_CREDENTIALS)

    def check_filter_exists(self, sub):
        # Check the usage states, operationStates
        for filter_type in ["operation_states", "usage_states"]:
            if not is_filter_type_equal(getattr(self, filter_type),
                                        ast.literal_eval(getattr(sub, filter_type))):
                return False
        # If all the above types are same then check id filter
        for id_filter in ["vnfd_id", "vnf_pkg_id"]:
            if not is_filter_type_equal(getattr(self, id_filter),
                                        ast.literal_eval(getattr(sub, id_filter))):
                return False
        return True

    def check_valid(self):
        links = ""
        logger.debug("SubscribeNotification--post::> Checking DB if "
                     "callbackUri already exists")
        subscriptions = VnfPkgSubscriptionModel.objects.filter(callback_uri=self.callback_uri)
        for subscription in subscriptions:
            if self.check_filter_exists(subscription):
                links = json.loads(subscription.links)
                logger.error("Subscriptions has already exists with the same callbackUri and filter:%s" % links)
                raise VnfPkgDuplicateSubscriptionException(
                    "/%s" % (links["self"]["href"]))

        return True

    def save_db(self):
        """
        Save the subscription info to DB
        :return:
        """
        logger.debug("SubscribeNotification--post::> Saving the subscription "
                     "%s to the database" % self.subscription_id)
        links = {
            "self": {
                "href": os.path.join(const.VNFPKG_SUBSCRIPTION_ROOT_URI, self.subscription_id)
            }
        }
        VnfPkgSubscriptionModel.objects.create(
            subscription_id=self.subscription_id,
            callback_uri=self.callback_uri,
            notification_types=json.dumps(self.notification_types),
            auth_info=json.dumps(self.authentication),
            usage_states=json.dumps(self.usage_states),
            operation_states=json.dumps(self.operation_states),
            vnf_products_from_provider=json.dumps(self.vnf_products_from_provider),
            vnfd_id=json.dumps(self.vnfd_id),
            vnf_pkg_id=json.dumps(self.vnf_pkg_id),
            links=json.dumps(links))
        logger.debug('Create Subscription[%s] success', self.subscription_id)


class QuerySubscription(object):
    """
    The class for query subscription
    """
    def query_multi_subscriptions(self, params):
        """
        Query subscriptions
        :param params:
        :return:
        """
        query_data = {}
        logger.debug("QuerySubscription--get--multi--subscriptions--biz::> Check "
                     "for filter in query params %s" % params)
        for query, value in list(params.items()):
            if query in ROOT_FILTERS:
                query_data[ROOT_FILTERS[query] + '__icontains'] = value
        # Query the database with filter if the request has fields in request params, else fetch all records
        if query_data:
            subscriptions = VnfPkgSubscriptionModel.objects.filter(**query_data)
        else:
            subscriptions = VnfPkgSubscriptionModel.objects.all()
        if not subscriptions.exists():
            return []
        return [subscription.toDict() for subscription in subscriptions]

    def query_single_subscription(self, subscription_id):
        """
        Query subscription by id
        :param subscription_id:
        :return:
        """
        logger.debug("QuerySingleSubscriptions--get--single--subscription--biz::> "
                     "ID: %s" % subscription_id)

        subscription = VnfPkgSubscriptionModel.objects.filter(
            subscription_id=subscription_id)
        if not subscription.exists():
            raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
                                                     "does not exist" % subscription_id)
        return subscription[0].toDict()


class TerminateSubscription(object):
    """
    The class to terminate the subscription
    """
    def terminate(self, subscription_id):
        logger.debug("TerminateSubscriptions--delete--biz::> "
                     "ID: %s" % subscription_id)

        subscription = VnfPkgSubscriptionModel.objects.filter(
            subscription_id=subscription_id)
        if not subscription.exists():
            raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
                                                     "does not exist" % subscription_id)
        subscription[0].delete()

<filename>catalog/packages/biz/vnf_pkg_subscription.py
# Copyright (C) 2019 Verizon. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import ast
import json
import logging
import os
import uuid
from collections import Counter

import requests
from requests.auth import HTTPBasicAuth
from rest_framework import status

from catalog.packages import const
from catalog.pub.database.models import VnfPkgSubscriptionModel
from catalog.pub.exceptions import VnfPkgSubscriptionException, \
    VnfPkgDuplicateSubscriptionException, SubscriptionDoesNotExistsException
from catalog.pub.utils.values import ignore_case_get

logger = logging.getLogger(__name__)

ROOT_FILTERS = {
    "notificationTypes": "notification_types",
    "vnfdId": "vnfd_id",
    "vnfPkgId": "vnf_pkg_id",
    "operationalState": "operation_states",
    "usageState": "usage_states"
}


def is_filter_type_equal(new_filter, existing_filter):
    return Counter(new_filter) == Counter(existing_filter)


class CreateSubscription(object):
    """
    Create subscription info
    """
    def __init__(self, data):
        self.data = data
        self.filter = ignore_case_get(self.data, "filter", {})
        self.callback_uri = ignore_case_get(self.data, "callbackUri")
        self.authentication = ignore_case_get(self.data, "authentication", {})
        self.notification_types = ignore_case_get(self.filter, "notificationTypes", [])
        self.operation_states = ignore_case_get(self.filter, "operationalState", [])
        self.usage_states = ignore_case_get(self.filter, "usageState", [])
        self.vnfd_id = ignore_case_get(self.filter, "vnfdId", [])
        self.vnf_pkg_id = ignore_case_get(self.filter, "vnfPkgId", [])
        self.vnf_products_from_provider = \
            ignore_case_get(self.filter, "vnfProductsFromProviders", [])

    def check_callbackuri_connection(self):
        """
        Check if the callback uri can access
        :return:
        """
        logger.debug("SubscribeNotification-post::> Sending GET request "
                     "to %s" % self.callback_uri)
        try:
            if self.authentication:
                if const.BASIC in self.authentication.get("authType", ''):
                    params = self.authentication.get("paramsBasic", {})
                    username = params.get("userName")
                    password = <PASSWORD>("password")
                    response = requests.get(self.callback_uri, auth=HTTPBasicAuth(username, password), timeout=2,
                                            verify=False)
                elif const.OAUTH2_CLIENT_CREDENTIALS in self.authentication.get("authType", ''):
                    # todo
                    pass
                else:
                    # todo
                    pass
            else:
                response = requests.get(self.callback_uri, timeout=2, verify=False)
            if response.status_code != status.HTTP_204_NO_CONTENT:
                raise VnfPkgSubscriptionException(
                    "callbackUri %s returns %s status code." % (
                        self.callback_uri,
                        response.status_code
                    )
                )
        except Exception:
            raise VnfPkgSubscriptionException(
                "callbackUri %s didn't return 204 status code." % self.callback_uri
            )

    def do_biz(self):
        """
        Do business
        :return:
        """
        self.subscription_id = str(uuid.uuid4())
        self.check_valid_auth_info()
        self.check_callbackuri_connection()
        self.check_valid()
        self.save_db()
        subscription = VnfPkgSubscriptionModel.objects.get(
            subscription_id=self.subscription_id
        )
        if subscription:
            return subscription.toDict()

    def check_valid_auth_info(self):
        """
        Check if the Auth info is valid
        :return:
        """
        logger.debug("SubscribeNotification--post::> Validating Auth "
                     "details if provided")
        if self.authentication.get("paramsBasic", {}) and const.BASIC not in self.authentication.get("authType"):
            raise VnfPkgSubscriptionException('Auth type should be ' + const.BASIC)
        if self.authentication.get("paramsOauth2ClientCredentials", {}) \
                and const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"):
            raise VnfPkgSubscriptionException('Auth type should be ' + const.OAUTH2_CLIENT_CREDENTIALS)

    def check_filter_exists(self, sub):
        # Check the usage states, operationStates
        for filter_type in ["operation_states", "usage_states"]:
            if not is_filter_type_equal(getattr(self, filter_type),
                                        ast.literal_eval(getattr(sub, filter_type))):
                return False
        # If all the above types are same then check id filter
        for id_filter in ["vnfd_id", "vnf_pkg_id"]:
            if not is_filter_type_equal(getattr(self, id_filter),
                                        ast.literal_eval(getattr(sub, id_filter))):
                return False
        return True

    def check_valid(self):
        links = ""
        logger.debug("SubscribeNotification--post::> Checking DB if "
                     "callbackUri already exists")
        subscriptions = VnfPkgSubscriptionModel.objects.filter(callback_uri=self.callback_uri)
        for subscription in subscriptions:
            if self.check_filter_exists(subscription):
                links = json.loads(subscription.links)
                logger.error("Subscriptions has already exists with the same callbackUri and filter:%s" % links)
                raise VnfPkgDuplicateSubscriptionException(
                    "/%s" % (links["self"]["href"]))

        return True

    def save_db(self):
        """
        Save the subscription info to DB
        :return:
        """
        logger.debug("SubscribeNotification--post::> Saving the subscription "
                     "%s to the database" % self.subscription_id)
        links = {
            "self": {
                "href": os.path.join(const.VNFPKG_SUBSCRIPTION_ROOT_URI, self.subscription_id)
            }
        }
        VnfPkgSubscriptionModel.objects.create(
            subscription_id=self.subscription_id,
            callback_uri=self.callback_uri,
            notification_types=json.dumps(self.notification_types),
            auth_info=json.dumps(self.authentication),
            usage_states=json.dumps(self.usage_states),
            operation_states=json.dumps(self.operation_states),
            vnf_products_from_provider=json.dumps(self.vnf_products_from_provider),
            vnfd_id=json.dumps(self.vnfd_id),
            vnf_pkg_id=json.dumps(self.vnf_pkg_id),
            links=json.dumps(links))
        logger.debug('Create Subscription[%s] success', self.subscription_id)


class QuerySubscription(object):
    """
    The class for query subscription
    """
    def query_multi_subscriptions(self, params):
        """
        Query subscriptions
        :param params:
        :return:
        """
        query_data = {}
        logger.debug("QuerySubscription--get--multi--subscriptions--biz::> Check "
                     "for filter in query params %s" % params)
        for query, value in list(params.items()):
            if query in ROOT_FILTERS:
                query_data[ROOT_FILTERS[query] + '__icontains'] = value
        # Query the database with filter if the request has fields in request params, else fetch all records
        if query_data:
            subscriptions = VnfPkgSubscriptionModel.objects.filter(**query_data)
        else:
            subscriptions = VnfPkgSubscriptionModel.objects.all()
        if not subscriptions.exists():
            return []
        return [subscription.toDict() for subscription in subscriptions]

    def query_single_subscription(self, subscription_id):
        """
        Query subscription by id
        :param subscription_id:
        :return:
        """
        logger.debug("QuerySingleSubscriptions--get--single--subscription--biz::> "
                     "ID: %s" % subscription_id)

        subscription = VnfPkgSubscriptionModel.objects.filter(
            subscription_id=subscription_id)
        if not subscription.exists():
            raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
                                                     "does not exist" % subscription_id)
        return subscription[0].toDict()


class TerminateSubscription(object):
    """
    The class to terminate the subscription
    """
    def terminate(self, subscription_id):
        logger.debug("TerminateSubscriptions--delete--biz::> "
                     "ID: %s" % subscription_id)

        subscription = VnfPkgSubscriptionModel.objects.filter(
            subscription_id=subscription_id)
        if not subscription.exists():
            raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
                                                     "does not exist" % subscription_id)
        subscription[0].delete()

# Copyright (C) 2019 Verizon. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #         http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Create subscription info Check if the callback uri can access         :return: # todo # todo Do business         :return: Check if the Auth info is valid         :return: # Check the usage states, operationStates # If all the above types are same then check id filter Save the subscription info to DB         :return: The class for query subscription Query subscriptions         :param params:         :return: # Query the database with filter if the request has fields in request params, else fetch all records Query subscription by id         :param subscription_id:         :return: The class to terminate the subscription