# -*- coding: utf-8 -*-
# Copyright (2017) Hewlett Packard Enterprise Development LP
#
# 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.
# Python libs
import collections
import configparser
import glob
import json
import logging
import logging.config
import OpenSSL
import os
import socket
# 3rd party libs
from hpOneView.oneview_client import OneViewClient
# Modules own libs
from oneview_redfish_toolkit.api.errors import OneViewRedfishError
from oneview_redfish_toolkit.api.errors \
    import OneViewRedfishResourceNotAccessibleError
from oneview_redfish_toolkit.api.errors \
    import OneViewRedfishResourceNotFoundError
globals()['subscriptions_by_type'] = {
    "StatusChange": {},
    "ResourceUpdated": {},
    "ResourceAdded": {},
    "ResourceRemoved": {},
    "Alert": {}
}
globals()['all_subscriptions'] = {}
[docs]def load_config(conf_file):
    """Loads redfish.conf file
        Loads and parsers the system conf file into config global var
        Loads json schemas into schemas_dict global var
        Established a connection with OneView and sets in as ov_conn
        global var
        Args:
            conf_file: string with the conf file name
        Returns:
            None
        Exception:
            OneViewRedfishResourceNotFoundError:
                - if conf file not found
                - if any of the schemas files are not found
                - if the schema directory is not found
            OneViewRedFishResourceNotAccessibleError:
                - if can't access schema's directory
            HPOneViewException:
                - if fails to connect to oneview
    """
    config = load_conf(conf_file)
    globals()['config'] = config
    # Config file read set global vars
    # Setting ov_config
    ov_config = dict(config.items('oneview_config'))
    ov_config['credentials'] = dict(config.items('credentials'))
    ov_config['api_version'] = int(ov_config['api_version'])
    globals()['ov_config'] = ov_config
    # Setting schemas_dict
    schemas = dict(config.items('schemas'))
    globals()['schemas'] = schemas
    registries = dict(config.items('registry'))
    load_event_service_info()
    # Load schemas | Store schemas | Connect to OneView
    try:
        ov_client = OneViewClient(ov_config)
        globals()['ov_client'] = ov_client
        registry_dict = load_registry(
            config['redfish']['registry_dir'],
            registries)
        globals()['registry_dict'] = registry_dict
        store_schemas(config['redfish']['schema_dir'])
    except OneViewRedfishResourceNotFoundError as e:
        raise OneViewRedfishError(
            'Failed to load schemas or registries: {}'.format(e)
        )
    except Exception as e:
        raise OneViewRedfishError(
            'Failed to connect to OneView: {}'.format(e)
        ) 
[docs]def load_conf(conf_file):
    """Loads and parses conf file
        Loads and parses the module conf file
        Args:
            conf_file: string with the conf file name
        Returns:
            ConfigParser object with conf_file configs
        Exception:
            OneViewRedfishResourceNotFoundError:
                - if conf file not found
    """
    if not os.path.isfile(conf_file):
        raise OneViewRedfishResourceNotFoundError(conf_file, 'File')
    config = configparser.ConfigParser()
    config.optionxform = str
    try:
        config.read(conf_file)
    except Exception:
        raise
    return config 
[docs]def load_event_service_info():
    """Loads Event Service information
        Loads DeliveryRetryAttempts and DeliveryRetryIntervalSeconds
        from CONFIG file and store it in a global var.
        Exceptions:
            OneViewRedfishError: DeliveryRetryAttempts and
            DeliveryRetryIntervalSeconds must be integers greater than zero.
    """
    config = globals()['config']
    event_service = dict(config.items("event_service"))
    try:
        delivery_retry_attempts = \
            
int(event_service["DeliveryRetryAttempts"])
        delivery_retry_interval = \
            
int(event_service["DeliveryRetryIntervalSeconds"])
        if delivery_retry_attempts <= 0 or delivery_retry_interval <= 0:
            raise OneViewRedfishError(
                "DeliveryRetryAttempts and DeliveryRetryIntervalSeconds must"
                " be an integer greater than zero.")
    except ValueError:
        raise OneViewRedfishError(
            "DeliveryRetryAttempts and DeliveryRetryIntervalSeconds "
            "must be valid integers.")
    globals()['delivery_retry_attempts'] = delivery_retry_attempts
    globals()['delivery_retry_interval'] = delivery_retry_interval 
[docs]def load_registry(registry_dir, registries):
    """Loads Registries
        Loads all registries listed in the config file using registry_dir
        directory
        Args:
            registry_dir: string with the directory to load registries from
            registries: dict with registry name as key and registry file_name
                as value. The key will also be the key in the returning dict.
        Returns:
            OrderedDict: A dict containing 'RegistryName': registry_obj
        Exceptions:
            OneviewRedfishResourceNotFoundError:
                - if registry_dir is not found
                - any of json files is not found
            OneviewRedfishResourceNotAccessible:
                - if registry_dir is can't be accessed
    """
    if os.path.isdir(registry_dir) is False:
        raise OneViewRedfishResourceNotFoundError(
            registry_dir, 'Directory')
    if os.access(registry_dir, os.R_OK) is False:
        raise OneViewRedfishResourceNotAccessibleError(
            registry_dir, 'directory')
    registries_dict = collections.OrderedDict()
    for key in registries:
        try:
            with open(registry_dir + '/' + registries[key]) as f:
                registries_dict[key] = json.load(f)
        except Exception:
            raise OneViewRedfishResourceNotFoundError(
                registries[key], 'File')
    return registries_dict 
[docs]def store_schemas(schema_dir):
    """Stores all DMTF JSON Schemas
        Stores all schemas listed in schemas searching schema_dir directory.
        Args:
            schema_dir: String with the directory to load schemas from.
        Returns:
            Dictionary: A dict containing ('http://redfish.dmtf.org/schemas/
                        v1/<schema_file_name>': schema_obj) pairs
    """
    schema_paths = glob.glob(schema_dir + '/*.json')
    if not schema_paths:
        raise OneViewRedfishResourceNotFoundError(
            "JSON Schemas", "File")
    stored_schemas = dict()
    for path in schema_paths:
        with open(path) as schema_file:
            json_schema = json.load(schema_file)
        if os.name == 'nt':
            file_name = path.split('\\')[-1]
        else:
            file_name = path.split('/')[-1]
        stored_schemas["http://redfish.dmtf.org/schemas/v1/" + file_name] = \
            
json_schema
    globals()['stored_schemas'] = stored_schemas 
[docs]def get_oneview_client(session_id=None, is_service_root=False):
    """Establishes a OneView connection to be used in the module
        Establishes a OV connection if one does not exists.
        If one exists, do a single OV access to check if its sill
        valid. If not tries to establish a new connection.
        Sets the connection on the ov_conn global var
        Args:
            session_id: The ID of a valid authenticated session, if the
            authentication_mode is session. Defaults to None.
            is_service_root: Informs if who is calling this function is the
            ServiceRoot blueprint. If true, even if authentication_mode is
            set to session it will use the information on the conf file to
            return a connection.  This is a workaround to allow ServiceRoot
            to retrieve the appliance UUID before user logs in.
        Returns:
            OneViewClient object
        Exceptions:
            HPOneViewException if can't connect or reconnect to OV
    """
    config = globals()['config']
    auth_mode = config["redfish"]["authentication_mode"]
    if auth_mode == "conf" or is_service_root:
        # Doing conf based authentication
        ov_client = globals()['ov_client']
        ov_config = globals()['ov_config']
        # Check if connection is ok yet
        try:
            ov_client.connection.get('/rest/logindomains')
            return ov_client
        # If expired try to make a new connection
        except Exception:
            try:
                logging.exception('Re-authenticated')
                ov_client.connection.login(ov_config['credentials'])
                return ov_client
            # if failed abort
            except Exception:
                raise
    else:
        # Auth mode is session
        oneview_config = dict(config.items('oneview_config'))
        oneview_config['credentials'] = {"sessionID": session_id}
        oneview_config['api_version'] = int(oneview_config['api_version'])
        try:
            oneview_client = OneViewClient(oneview_config)
            oneview_client.connection.get('/rest/logindomains')
            return oneview_client
        except Exception:
            logging.exception("Failed to recover session based connection")
            raise 
[docs]def get_ip():
    """Tries to detect default route IP Address"""
    s = socket.socket(type=socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 1))
        ip = s.getsockname()[0]
    except Exception as e:
        logging.exception(e)
        ip = "127.0.0.1"
    finally:
        s.close()
    return ip 
[docs]def generate_certificate(dir_name, file_name, key_length, key_type="rsa"):
    """Create self-signed cert and key files
        Args:
            dir_name: name of the directory to store the files
            file_name: name of the files that will be created. It will append
                .crt to certificate file and .key to key file
            key_length: key length in bits
            key_type: crypto type: RSA or DSA; defaults to RSA
        Returns:
            Nothing
        Exceptions:
            Raise exceptions on error
    """
    config = globals()['config']
    private_key = OpenSSL.crypto.PKey()
    if key_type == "rsa":
        private_key.generate_key(OpenSSL.crypto.TYPE_RSA, key_length)
    elif key_type == "dsa":
        private_key.generate_key(OpenSSL.crypto.TYPE_DSA, key_length)
    else:
        message = "Invalid key_type"
        logging.error(message)
        raise OneViewRedfishError(message)
    if not config.has_option("ssl-cert-defaults", "commonName"):
        config["ssl-cert-defaults"]["commonName"] = get_ip()
    cert = OpenSSL.crypto.X509()
    cert_subject = cert.get_subject()
    cert_defaults = dict(config.items("ssl-cert-defaults"))
    for key, value in cert_defaults.items():
        setattr(cert_subject, key, value)
    cert.set_serial_number(1)
    cert.gmtime_adj_notBefore(0)
    cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
    cert.set_issuer(cert.get_subject())
    cert.set_pubkey(private_key)
    cert.sign(private_key, "sha1")
    # Save Files
    with open(os.path.join(dir_name, file_name + ".crt"), "wt") as f:
        f.write(OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_PEM, cert).decode("UTF-8"))
    with open(os.path.join(dir_name, file_name + ".key"), "wt") as f:
        f.write(OpenSSL.crypto.dump_privatekey(
            OpenSSL.crypto.FILETYPE_PEM, private_key).decode("UTF-8"))