# encoding: utf-8
"""
Represents a connection to the RDS service.
"""                    

import warnings

import six
import time
import json

from footmark.connection import ACSQueryConnection
from footmark.rds.regioninfo import RegionInfo
from footmark.exception import RDSResponseError


class RDSConnection(ACSQueryConnection):
    SDKVersion = '2014-08-15'
    DefaultRegionId = 'cn-hangzhou'
    DefaultRegionName = u'杭州'.encode("UTF-8")
    ResponseError = RDSResponseError

    def __init__(self, acs_access_key_id=None, acs_secret_access_key=None,
                 region=None, sdk_version=None, security_token=None):
        """
        Init method to create a new connection to RDS.
        """
        if not region:
            region = RegionInfo(self, self.DefaultRegionName,
                                self.DefaultRegionId)
        self.region = region
        if sdk_version:
            self.SDKVersion = sdk_version

        self.RDSSDK = 'aliyunsdkrds.request.v' + self.SDKVersion.replace('-', '')

        super(RDSConnection, self).__init__(acs_access_key_id,
                                            acs_secret_access_key,
                                            self.region, self.RDSSDK, security_token)

    def create_rds_instance(self, db_engine, engine_version, db_instance_class, db_instance_storage,
                            instance_net_type, security_ip_list, pay_type, period=None,zone=None,
                            instance_description=None, used_time=None, instance_network_type=None, connection_mode=None,
                            vpc_id=None, vswitch_id=None, private_ip_address=None, allocate_public_ip=False,
                            connection_string_prefix=None, public_port=None, db_name=None, db_description=None,
                            character_set_name=None, maint_window=None, preferred_backup_time=None,
                            preferred_backup_period=None, backup_retention_period=None, db_tags=None, wait=None,
                            wait_timeout=None):
        """
         Create RDS Instance

         :type zone: string
         :param zone:  ID of a zone to which an instance belongs
         :type db_engine: string
         :param db_engine: The type of database
         :type engine_version: string
         :param engine_version: Version number of the database engine to use
         :type db_instance_class: string
         :param db_instance_class: The instance type of the database.
         :type db_instance_storage: integer
         :param db_instance_storage: Size in gigabytes of the initial storage for the DB instance.
         :type instance_net_type: string
         :param instance_net_type: The net type of the DB instance
         :type instance_description: string
         :param instance_description: Instance description or remarks, no more than 256 bytes.
         :type security_ip_list: string
         :param security_ip_list: List of IP addresses allowed to access all databases of an instance.
         :type pay_type: string
         :param pay_type: The pay type of the DB instance.
         :type period: string
         :param period: Period of the instance if pay_type set to prepaid.
         :type used_time: integer
         :param used_time: This parameter specifies the duration for purchase.
         :type instance_network_type: string
         :param instance_network_type: The network type of the instance.
         :type connection_mode: string
         :param connection_mode: The connection mode of the instance
         :type vpc_id: string
         :param vpc_id: The ID of the VPC
         :type vswitch_id: string
         :param vswitch_id: ID of VSwitch
         :type private_ip_address: string
         :param private_ip_address: IP address of an VPC under VSwitchId.
         :type allocate_public_ip: string
         :param allocate_public_ip: Whether to allocate public IP
         :type connection_string_prefix: string
         :param connection_string_prefix: Prefix of an Internet connection string
         :type public_port: integer
         :param public_port: The public connection port.
         :type db_name: string
         :param db_name: Name of a database to create within the instance.
         :type db_description: string
         :param db_description: Description of a database to create within the instance.
         :type character_set_name: string
         :param character_set_name: Associate the DB instance with a specified character set.
         :type maint_window: string
         :param maint_window: Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi.
         :type preferred_backup_time: string
         :param preferred_backup_time: Backup time, in the format of HH:mmZ- HH:mm Z.
         :type preferred_backup_period: string
         :param preferred_backup_period: Backup period.
         :type backup_retention_period: integer
         :param backup_retention_period: Retention days of the backup
         :type db_tags: dict
         :param db_tags: A list of hash/dictionaries of db tags
         :type db_engine: string
         :param wait: Wait for the RDS instance to be 'running' before returning.
         :type wait_timeout: integer
         :param wait_timeout: how long before wait gives up, in seconds
         :return:
             changed: If instance is created successfully the changed will be set
                 to True else False
             DBInstanceId: the newly created instance id
         """
        params = {}
        results = []
        flag = False
        changed = False

        if zone:
            self.build_list_params(params, zone, 'ZoneId')
        if db_engine:
            self.build_list_params(params, db_engine, 'Engine')
        if engine_version:
            self.build_list_params(params, engine_version, 'EngineVersion')
        if db_instance_class:
            self.build_list_params(params, db_instance_class, 'DBInstanceClass')
        if db_instance_storage:
            self.build_list_params(params, db_instance_storage, 'DBInstanceStorage')
        if instance_net_type:
            self.build_list_params(params, instance_net_type, 'DBInstanceNetType')
        if instance_description:
            self.build_list_params(params, instance_description, 'DBInstanceDescription')
        if security_ip_list:
            self.build_list_params(params, security_ip_list, 'SecurityIPList')
        if pay_type:
            self.build_list_params(params, pay_type, 'PayType')
        if period:
            self.build_list_params(params, period, 'Period')
        if used_time:
            self.build_list_params(params, used_time, 'UsedTime')
        if instance_network_type:
            self.build_list_params(params, instance_network_type, 'InstanceNetworkType')
        if connection_mode:
            self.build_list_params(params, connection_mode, 'ConnectionMode')
        if vpc_id:
            self.build_list_params(params, vpc_id, 'VPCId')
        if vswitch_id:
            self.build_list_params(params, vswitch_id, 'VSwitchId')
        if private_ip_address:
            self.build_list_params(params, private_ip_address, 'PrivateIpAddress')
        try:
            results_instance = self.get_status('CreateDBInstance', params)
            if results_instance:
                results.append({"DBInstanceId": results_instance["DBInstanceId"]})
            changed = True
        except Exception as ex:
            custom_msg = [
                "The API is showing None error code and error message while creating rds instance",
                "Following error occur while creating rds instance: ",
                "Failed to create rds instance with error code "
            ]
            results = results + self.rds_error_handler(ex, custom_msg)

        else:          
            time.sleep(240)
            # Start newly created Instance
            # wait until instance status becomes running         
            if allocate_public_ip and self.check_instance_status(results_instance[u'DBInstanceId']):
                if connection_string_prefix and public_port:
                    if instance_net_type == "Intranet":
                        changed_conn_str, result_conn_str = \
                            self.create_instance_public_connection_string(results_instance[u'DBInstanceId'],
                                                                          connection_string_prefix, public_port)
                    elif instance_net_type == "Internet":
                        changed_conn_str, result_conn_str = \
                            self.modify_instance_public_connection(results_instance[u'DBInstanceId'], 
                                                                   results_instance[u'ConnectionString'],
                                                                   connection_string_prefix, public_port)
            
                    if 'error' in (''.join(str(result_conn_str))).lower():
                        results.append(result_conn_str)
                           
            if self.check_instance_status(results_instance[u'DBInstanceId']):
                if db_name or character_set_name: 
                    changed_create_db, result_create_db = self.create_database(results_instance[u'DBInstanceId'],
                                                                               db_name, db_description,
                                                                               character_set_name)
            
                    if 'error' in (''.join(str(result_create_db))).lower():
                        results.append(result_create_db)                      
              
            if maint_window and self.check_instance_status(results_instance[u'DBInstanceId']):
                changed_maint_window, result_maint_window = \
                    self.modify_db_instance_maint_time(results_instance[u'DBInstanceId']
                                                            , maint_window)
                if 'error' in (''.join(str(result_maint_window))).lower():
                    results.append(result_maint_window) 
            
            if preferred_backup_time and preferred_backup_period:               
                if self.check_instance_status(results_instance[u'DBInstanceId']):
                    changed_backup_policy, result_backup_policy = \
                        self.modify_backup_policy(results_instance[u'DBInstanceId'],
                                                  preferred_backup_time, preferred_backup_period,
                                                  backup_retention_period)
                    if 'error' in (''.join(str(result_backup_policy))).lower():
                        results.append(result_backup_policy) 
               
            if db_tags:
                changed_tags, result_db_tags = self.bind_tags(results_instance[u'DBInstanceId'], db_tags) 
                if 'error' in (''.join(str(result_db_tags))).lower():
                    results.append(result_db_tags)                                    

            if str(wait).lower() in ['yes', 'true'] and wait_timeout:
                time.sleep(wait_timeout)

        return changed, results

    def create_rds_read_only_instance(self, source_instance, zone, engine_version, db_instance_class,
                                      db_instance_storage, instance_description, pay_type, instance_network_type=None,
                                      vpc_id=None, vswitch_id=None, private_ip_address=None):
        """
        Create RDS Read-Only Instance

        :type source_instance: string
        :param source_instance: ID of the database to replicate.
        :type zone: string
        :param zone:  ID of a zone to which an instance belongs
        :type engine_version: string
        :param engine_version: Version number of the database engine to use
        :type db_instance_class: string
        :param db_instance_class: The instance type of the database.
        :type db_instance_storage: integer
        :param db_instance_storage: Size in gigabytes of the initial storage for the DB instance.
        :type instance_description: string
        :param instance_description: Instance description or remarks, no more than 256 bytes.
        :type pay_type: string
        :param pay_type: The pay type of the DB instance.
        :type instance_network_type: string
        :param instance_network_type: The network type of the instance.
        :type vpc_id: string
        :param vpc_id: The ID of the VPC
        :type vswitch_id: string
        :param vswitch_id: ID of VSwitch
        :type private_ip_address: string
        :param private_ip_address: IP address of an VPC under VSwitchId.
        :return:
            changed: If ready-only instance is created successfully the changed will be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False

        if zone:
            self.build_list_params(params, zone, 'ZoneId')
        if source_instance:
            self.build_list_params(params, source_instance, 'DBInstanceId')
        if engine_version:
            self.build_list_params(params, engine_version, 'EngineVersion')
        if db_instance_class:
            self.build_list_params(params, db_instance_class, 'DBInstanceClass')
        if db_instance_storage:
            self.build_list_params(params, db_instance_storage, 'DBInstanceStorage')
        if instance_description:
            self.build_list_params(params, instance_description, 'DBInstanceDescription')
        if pay_type:
            self.build_list_params(params, pay_type, 'PayType')
        if instance_network_type:
            self.build_list_params(params, instance_network_type, 'InstanceNetworkType')
        if vpc_id:
            self.build_list_params(params, vpc_id, 'VPCId')
        if vswitch_id:
            self.build_list_params(params, vswitch_id, 'VSwitchId')
        if private_ip_address:
            self.build_list_params(params, private_ip_address, 'PrivateIpAddress')
        try:
            results = self.get_status('CreateReadOnlyDBInstance', params)
            changed = True
        except Exception as ex:
            error_code = str(ex.error_code)
            error_msg = str(ex.message)
            results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def get_rds_instances_info(self, instance_id):
        """
        Get RDS Instance Info
        :type instance_id: string
        :param instance_id: Id of instance
        :return:
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
             
        try:
            results = self.get_status('DescribeDBInstances', params)            
        except Exception as ex:
            error_code = str(ex.error_code)
            error_msg = str(ex.message)
            results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def modify_rds_instance(self, instance_id, current_connection_string, connection_string_prefix, port,
                            connection_mode, db_instance_class, db_instance_storage, pay_type, instance_description,
                            security_ip_list, instance_network_type, vpc_id, vswitch_id, maint_window,
                            preferred_backup_time, preferred_backup_period, backup_retention_period):
        """
        Modify RDS Instance

        :type  instance_id: string
        :param instance_id: Id of the Instance to modify
        :type  current_connection_string: string
        :param current_connection_string: Current connection string of an instance.
        :type  connection_string_prefix: string
        :param connection_string_prefix: Target connection string
        :type  port: integer
        :param port: Target port
        :type  connection_mode: string
        :param connection_mode: Connection mode of the RDS Instance
        :type  db_instance_class: string
        :param db_instance_class: The instance type of the database
        :type  db_instance_storage: integer
        :param db_instance_storage: Size in gigabytes of the DB instance to change
        :type  pay_type: string
        :param pay_type: The pay type of the DB instance.
        :type  instance_description: string
        :param instance_description: Instance description or remarks, no more than 256 bytes
        :type  security_ip_list: string
        :param security_ip_list: List of IP addresses allowed to access all databases of an instance
        :type  instance_network_type: string
        :param instance_network_type: The network type of the instance.
        :type  vpc_id: string
        :param vpc_id: The ID of the VPC.
        :type  vswitch_id: string
        :param vswitch_id: ID of VSwitch
        :type  maint_window: string
        :param maint_window: Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi.
        :type  preferred_backup_time: string
        :param preferred_backup_time: Backup time, in the format ofHH:mmZ- HH:mm Z.
        :type  preferred_backup_period: string
        :param preferred_backup_period: Backup period
        :type  backup_retention_period: integer
        :param backup_retention_period: Retention days of the backup
        :return:
            changed: If instance is modified successfully. the changed para will be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        perform_flag = True               
        try:
            instance_status = "Stopped"
            while instance_status == "Stopped":
                try:
                    instance_info = self.get_rds_instances_info(instance_id)
                    if instance_info:
                        if int(instance_info[1][u'TotalRecordCount']) == 1:
                            instance_status = "Present"
                            break
                        else:
                            perform_flag = False
                            results.append({"Error Message": "Instance not found in specified region"})
                            break

                except Exception as ex:
                    instance_status = "Stopped" 
            if perform_flag:    
                if self.check_instance_status(instance_id):
                    changed_inst_type, result_inst_type = self.change_rds_instance_type(instance_id, db_instance_class,
                                                                                        db_instance_storage, pay_type)
                    if result_inst_type:
                        results.append(result_inst_type)
                    
                    if 'error' not in (''.join(str(result_inst_type))).lower():
                        changed = True
                    time.sleep(60)                                                                   

                if self.check_instance_status(instance_id):                                  
                    changed_security_ip, result_security_ip =\
                        self.modify_rds_instance_security_ip_list(instance_id, security_ip_list)

                    if 'error' in (''.join(str(result_security_ip))).lower():
                        results.append(result_security_ip)
                    else:
                        changed = True                      
                                 
                if preferred_backup_time and preferred_backup_period:
                    if self.check_instance_status(instance_id):        
                        changed_backup_policy, result_backup_policy = \
                            self.modify_backup_policy(instance_id, preferred_backup_time, preferred_backup_period,
                                                      backup_retention_period)
                        if 'error' in (''.join(str(result_backup_policy))).lower():
                            results.append(result_backup_policy)
                        else:
                            changed = True       
                                             
                if maint_window:
                    if self.check_instance_status(instance_id):     
                        changed_maint_time, result_maint_time = self.modify_db_instance_maint_time(instance_id,
                                                                                                   maint_window)
                        if 'error' in (''.join(str(result_maint_time))).lower():
                            results.append(result_maint_time)
                        else:
                            changed = True  
                                  
                if connection_mode:
                    if self.check_instance_status(instance_id):
                        changed_conn_mode, result_conn_mode = self.modify_rds_instance_access_mode(instance_id,
                                                                                                   connection_mode)
                    if 'error' in (''.join(str(result_conn_mode))).lower():
                        results.append(result_conn_mode)
                    else:
                        changed = True  
                    time.sleep(5)

                if instance_description:
                    if self.check_instance_status(instance_id):
                        changed_inst_desc, result_inst_desc = self.modify_rds_instance_description(instance_id,
                                                                                                   instance_description)
                        if 'error' in (''.join(str(result_inst_desc))).lower():
                            results.append(result_inst_desc)
                        else:
                            changed = True           
                              
                if current_connection_string:    
                    if self.check_instance_status(instance_id) and connection_string_prefix and port:                          
                        changed_public_conn, result_public_conn = \
                            self.modify_instance_public_connection(instance_id, current_connection_string,
                                                                   connection_string_prefix, port)
            
                        if 'error' in (''.join(str(result_public_conn))).lower():
                            results.append(result_public_conn)                          
                        else:
                            changed = True                       
                        time.sleep(5)    
                                       
                if instance_network_type and self.check_instance_status(instance_id):   
                    changed_net_type, result_net_type = self.modify_rds_instance_network_type(instance_id,
                                                                                              instance_network_type,
                                                                                              vpc_id, vswitch_id)
                    if 'error' in (''.join(str(result_net_type))).lower():
                        results.append(result_net_type)                        
                    else:
                        changed = True                      

        except Exception as ex:
            error_code = str(ex.error_code)
            error_msg = str(ex.message)
            results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def check_instance_status(self, instance_id):
        """
        Check RDS Instance Status
        
        :type  instance_id: string
        :param instance_id: Id of the Instance to modify
        :return: return true if instance status is running else false
        """
        status_flag = False
        instance_status = "Stopped"
        while instance_status == "Stopped":
            try:
                instance_info = self.get_rds_instances_info(instance_id)
                if instance_info:    
                    if instance_info[1][u'Items'][u'DBInstance'][0][u'DBInstanceStatus'] \
                            in ['running', 'Running']:
                        instance_status = instance_info[1][u'Items'][u'DBInstance'][0][u'DBInstanceStatus']
                        status_flag = True
                    else:
                        time.sleep(15)                           
            except Exception as ex:
                instance_status = "Stopped"           
        return status_flag

    def change_rds_instance_type(self, instance_id, db_instance_class, db_instance_storage, pay_type):
        """
        Change RDS Instance Type

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type db_instance_class: string
        :param db_instance_class: The instance type of the database
        :type db_instance_storage: integer
        :param db_instance_storage: Size in gigabytes to change of the DB instance.
        :type pay_type: string
        :param pay_type: The pay type of the DB instance.
        :return:
            changed: If instance is changed successfully. the changed para will be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False

        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if db_instance_class:
            self.build_list_params(params, db_instance_class, 'DBInstanceClass')
        if db_instance_storage:
            self.build_list_params(params, db_instance_storage, 'DBInstanceStorage')
        if pay_type:
            self.build_list_params(params, pay_type, 'PayType')
             
        try:
            results = self.get_status('ModifyDBInstanceSpec', params)  
            changed = True          
        except Exception as ex:
            custom_msg = [
                "The API is showing None error code and error message while changing rds instance type",
                "Following error occur while changing rds instance type: ",
                "Failed to change rds instance type with error code "
            ]
            results = results + self.rds_error_handler(ex, custom_msg)

        return changed, results
    
    def modify_rds_instance_access_mode(self, instance_id, connection_mode):
        """
        Modify RDS Instance Access Mode

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type  connection_mode: string
        :param connection_mode: Connection mode of the RDS Instance
        :return:
            changed: If access mode of instance changed successfully. the changed para will be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False  
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if connection_mode:
            self.build_list_params(params, connection_mode, 'ConnectionMode')                     
        try:

            results = self.get_status('ModifyDBInstanceConnectionMode', params)  
            changed = True          
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error "
                                                 "message while modifying instance access mode"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify instance access mode with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results
    
    def modify_rds_instance_network_type(self, instance_id, instance_network_type, vpc_id, vswitch_id,
                                         private_ip_address=None):
        """
        Modify RDS Instance Network Type

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type instance_network_type: string
        :param instance_network_type: The network type of the instance.
        :type vpc_id: string
        :param vpc_id: The ID of the VPC. Value depends on network_type.
        :type vswitch_id: string
        :param vswitch_id: ID of VSwitch
        :type private_ip_address: string
        :param private_ip_address: IP address of an VPC under VSwitchId.
        :return:
            changed: If network type of the instance modified successfully. the changed para will be set to True else
            False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False  
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if instance_network_type:
            self.build_list_params(params, instance_network_type, 'InstanceNetworkType')       
        if vpc_id:
            self.build_list_params(params, vpc_id, 'VPCId')
        if vswitch_id:
            self.build_list_params(params, vswitch_id, 'VSwitchId')   
        if private_ip_address:
            self.build_list_params(params, private_ip_address, 'PrivateIpAddress')
                                              
        try:

            results = self.get_status('ModifyDBInstanceNetworkType', params)  
            changed = True          
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error"
                                                 " message while modifying instance network type "})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify instance network type with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def modify_rds_instance_description(self, instance_id, instance_description):
        """
        Modify RDS Instance Description

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type instance_id: string
        :param instance_description: Instance description or remarks, no more than 256 bytes
        :return:
            changed: If rds instance description modified successfully. the changed para will be set to True else
            False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False  
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if instance_description:
            self.build_list_params(params, instance_description, 'DBInstanceDescription')       
                                              
        try:

            results = self.get_status('ModifyDBInstanceDescription', params)  
            changed = True          
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error "
                                                 "message while modifying instance description"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify instance description with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def modify_rds_instance_security_ip_list(self, instance_id, security_ip_list, db_instance_ip_array_name=None,
                                             db_instance_ip_array_attribute=None):
        """
        Modify RDS Instance Security Ip List

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type security_ip_list: list
        :param security_ip_list: List of IP addresses allowed to access all databases of an instance.
        :type db_instance_ip_array_name: string
        :param db_instance_ip_array_name: Name of an IP address white list array
        :type db_instance_ip_array_attribute: string
        :param db_instance_ip_array_attribute: This parameter differentiates attribute values
        :return:
            changed: If security ip list is modified successfully. the changed para will be set to True else
            False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False  
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if security_ip_list:
            self.build_list_params(params, security_ip_list, 'SecurityIps')       
        if db_instance_ip_array_name:
            self.build_list_params(params, db_instance_ip_array_name, 'DBInstanceIPArrayName')
        if db_instance_ip_array_attribute:
            self.build_list_params(params, db_instance_ip_array_attribute, 'DBInstanceIPArrayAttribute')   
                                              
        try:

            results = self.get_status('ModifySecurityIps', params)  
            changed = True          
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error "
                                                 "message modifying security ip list"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify security ip list with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def create_instance_public_connection_string(self, db_instance_id, public_connection_string_prefix, public_port):
        """
        Create Instance Public Connection String

        :type db_instance_id: string
        :param db_instance_id: Id of instances to change
        :type public_connection_string_prefix: string
        :param public_connection_string_prefix: Prefix of an Internet connection string
        :type public_port: integer
        :param public_port: The public connection port.
        :return:
            changed: If instance public connection string created successfully.The changed para will be set to True else
            False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')
        if public_connection_string_prefix:
            self.build_list_params(params, public_connection_string_prefix, 'ConnectionStringPrefix')
        if public_port:
            self.build_list_params(params, public_port, 'Port')
        try:
            results = self.get_status('AllocateInstancePublicConnection', params)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error message"
                                                 " while creating public connection string "})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to create public connection string with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def modify_instance_public_connection(self, db_instance_id, current_connection_string,
                                          connection_string_prefix, port):
        """
        Modify Instance Public connection string

        :type db_instance_id: string
        :param db_instance_id: Id of instances to change
        :type current_connection_string: string
        :param current_connection_string: Current connection string of an instance.
        :type connection_string_prefix: string
        :param connection_string_prefix: Prefix of an Internet connection string
        :type port: integer
        :param port: The public connection port.
        :return:
            changed: If instance public connection string modified successfully.The changed para will
             be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')
        if current_connection_string:
            self.build_list_params(params, current_connection_string, 'CurrentConnectionString')
        if connection_string_prefix:
            self.build_list_params(params, connection_string_prefix, 'ConnectionStringPrefix')
        if port:
            self.build_list_params(params, port, 'Port')
        try:
            results = self.get_status('ModifyDBInstanceConnectionString', params)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error message"
                                                 " while modifying public connection string "})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify public connection string with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def modify_db_instance_maint_time(self, instance_id, maint_window):
        """
        Modify Instance Maintenance Time
        :type instance_id: string
        :param instance_id: Id of instances to change
        :type maint_window: string
        :param maint_window: Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi.
        :return:
            changed: If instance maintenance time is modified successfully.The changed para will
             be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
                
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if maint_window:
            self.build_list_params(params, maint_window, 'MaintainTime')
        try:
            results = self.get_status('ModifyDBInstanceMaintainTime', params)            
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error message"
                                                 " while modifying maintenance time"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify maintenance time with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def modify_backup_policy(self, instance_id, preferred_backup_time, preferred_backup_period,
                             backup_retention_period, backup_log=None):
        """
        Modify Backup Policy

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type preferred_backup_time: string
        :param preferred_backup_time: Backup time, in the format ofHH:mmZ- HH:mm Z.
        :type preferred_backup_period: string
        :param preferred_backup_period: Backup period
        :type backup_retention_period: integer
        :param backup_retention_period: Retention days of the backup (7 to 730 days
        :type backup_log: string
        :param backup_log: The default value is Enable. You can change it to Disabled
        :return:
            changed: If backup policy is modified successfully.The changed para will
             be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if preferred_backup_time:
            self.build_list_params(params, preferred_backup_time, 'PreferredBackupTime')
        if preferred_backup_period:
            self.build_list_params(params, preferred_backup_period, 'PreferredBackupPeriod')
        if backup_retention_period:
            self.build_list_params(params, backup_retention_period, 'BackupRetentionPeriod')
        if backup_log:
            self.build_list_params(params, backup_log, 'BackupLog')
        try:
            results = self.get_status('ModifyBackupPolicy', params)            
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error message "
                                                 "while modifying backup policy"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to modify backup policy with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def bind_tags(self, instance_id, db_tags):
        """
        Bind Tags to Instance

        :type instance_id: string
        :param instance_id: Id of instances to change
        :type instance_id: dict
        :param db_tags: A dictionaries of db tags
        :return:
            changed: If tags binded to instance successfully.The changed para will
             be set to True else False
            result: detailed server response
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if db_tags:
            db_tags_json = json.dumps(db_tags)
        if db_tags_json:
            self.build_list_params(params, db_tags_json, 'Tags')
        try:
            results = self.get_status('AddTagsToResource', params)            
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack") or ex.error_code is None:
                results.append({"Error Message": "The API is showing None error code and error message "
                                                 "while binding tags"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append("Failed to bind tags with error code " + error_code +
                               " and message: " + error_msg)

        return changed, results

    def create_database(self, instance_id, db_name, db_description, character_set_name):
        """
        Creates a new database in an instance
        :type instance_id: str
        :param instance_id: Id of instances
        :type db_name: str
        :param db_name: Name of a database to create within the instance.  If not specified, then no database is created
        :type db_description: str
        :param db_description: Description of a database to create within the instance.  If not specified,
        then no database is created.
        :type character_set_name: str
        :param character_set_name: Associate the DB instance with a specified character set.
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if db_name:
            self.build_list_params(params, db_name, 'DBName')
        if character_set_name:
            self.build_list_params(params, character_set_name, 'CharacterSetName')
        if db_description:
            self.build_list_params(params, db_description, 'DBDescription')
        
        try:
            response = self.get_status('CreateDatabase', params)
            results.append(response)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def delete_database(self, instance_id, db_name):
        """
        Delete database
        :type instance_id: str
        :param instance_id: Id of instances
        :type db_name: str
        :param db_name: Name of a database to delete within the instance. If not specified, then no database is deleted
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        if db_name:
            self.build_list_params(params, db_name, 'DBName')
        try:
            response = self.get_status('DeleteDatabase', params)
            results.append(response)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def create_account(self, db_instance_id, account_name, account_password, description=None, account_type=None):
        """
        Create account for database
        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Operation account requiring a uniqueness check. It may consist of lower case letters,
        numbers and underlines, and must start with a letter and have no more than 16 characters.
        :type account_password: str
        :param account_password: Operation password. It may consist of letters, digits, or underlines,
        with a length of 6 to 32 characters
        :type description: str
        :param description: Account remarks, which cannot exceed 256 characters. NOTE: It cannot begin with http://,
        https:// . It must start with a Chinese character or English letter. It can include Chinese and English
        characters/letters, underlines (_), hyphens (-), and numbers. The length may be 2-256 characters
        :type account_type: str
        :param account_type: Privilege type of account. Normal: Common privilege;Super: High privilege;
        Default value is Normal.This parameter is valid for MySQL 5.5/5.6 only.
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')

        if account_name:
            self.build_list_params(params, account_name, 'AccountName')

        if account_password:
            self.build_list_params(params, account_password, 'AccountPassword')

        if description:
            self.build_list_params(params, description, 'AccountDescription')

        if account_type:
            self.build_list_params(params, account_type, 'AccountType')
        
        try:
            response = self.get_status('CreateAccount', params)
            results.append(response)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def reset_instance_password(self, db_instance_id, account_name, account_password):
        """
        Reset instance password
        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Name of an account
        :type account_password: str
        :param account_password: A new password. It may consist of letters, numbers, or underlines,
        with a length of 6 to 32 characters
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')

        if account_name:
            self.build_list_params(params, account_name, 'AccountName')

        if account_password:
            self.build_list_params(params, account_password, 'AccountPassword')
        
        try:
            response = self.get_status('ResetAccountPassword', params)
            results.append(response)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def restart_rds_instance(self, instance_id):
        """
        Restart rds instance
        :type instance_id: str
        :param instance_id: Id of instances to reboot
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        
        try:
            response = self.get_status('RestartDBInstance', params)
            results.append(response)
            changed = True
            time.sleep(120)
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def release_rds_instance(self, instance_id):
        """
        Release rds instance
        :type instance_id: str
        :param instance_id: Id of instances to remove
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if instance_id:
            self.build_list_params(params, instance_id, 'DBInstanceId')
        
        try:
            response = self.get_status('DeleteDBInstance', params)
            results.append(response)
            changed = True
        except Exception as ex:
            error_code = str(ex.error_code)
            error_msg = str(ex.message)
            results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def delete_account(self, db_instance_id, account_name):
        """
        Delete Account

        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Name of an account
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')
        if account_name:
            self.build_list_params(params, account_name, 'AccountName')
        try:
            results = self.get_status('DeleteAccount', params)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})  
                  
        return changed, results

    def grant_account_permissions(self, db_instance_id, account_name, db_name, account_privilege):
        """
        Grant Account Permissions

        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Name of an account
        :type db_name: str
        :param db_name: Name of a database.
        :type account_privilege: str
        :param account_privilege: Account permissions
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')
        if account_name:
            self.build_list_params(params, account_name, 'AccountName')
        if db_name:
            self.build_list_params(params, db_name, 'DBName')
        if account_privilege:
            self.build_list_params(params, account_privilege, 'AccountPrivilege')
        try:
            results = self.get_status('GrantAccountPrivilege', params)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def revoke_account_permissions(self, db_instance_id, account_name, db_name):
        """
        Revoke Account Permissions

        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Name of an account
        :type db_name: str
        :param db_name: Name of a database.
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False
        
        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')
        if account_name:
            self.build_list_params(params, account_name, 'AccountName')
        if db_name:
            self.build_list_params(params, db_name, 'DBName')
        try:
            results = self.get_status('RevokeAccountPrivilege', params)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def switch_between_primary_standby_database(self, instance_id, node_id, force):
        """
        Switch between primary and standby databases in rds instance
        :type instance_id: str
        :param instance_id: Id of instances to modify
        :type node_id: str
        :param node_id: Unique ID of a node
        :type force: str
        :param force: Yes: forced; No: unforced; default value: unforced
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False       

        try:
            self.build_list_params(params, instance_id, 'DBInstanceId')
            describe_response = self.get_status('DescribeDBInstanceHAConfig', params)
            if describe_response:
                for describe_config in describe_response[u'HostInstanceInfos'][u'NodeInfo']:
                    if describe_config[u'NodeType'] == 'Slave':
                        node = describe_config[u'NodeId']
                        break
                if node == node_id:
                    if node_id:
                        self.build_list_params(params, node_id, 'NodeId')
                    if force:
                        self.build_list_params(params, force, 'Force')
        
                    response = self.get_status('SwitchDBInstanceHA', params)
                    results.append(response)
                    changed = True
                    time.sleep(7)
                else:
                    results.append({"Error Message": "The specified node_id is not found"})

        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def reset_account(self, db_instance_id, account_name, account_password):
        """
        Reset account
        :type db_instance_id: str
        :param db_instance_id: Id of instance
        :type account_name: str
        :param account_name: Name of an account
        :type account_password: str
        :param account_password: Account password.
        :return: Result dict of operation
        """
        params = {}
        results = []
        changed = False

        if db_instance_id:
            self.build_list_params(params, db_instance_id, 'DBInstanceId')

        if account_name:
            self.build_list_params(params, account_name, 'AccountName')

        if account_password:
            self.build_list_params(params, account_password, 'AccountPassword')

        try:
            response = self.get_status('ResetAccountForPG', params)
            results.append(response)
            changed = True
        except Exception as ex:
            if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                    or (ex.message == "need more than 2 values to unpack"):
                results.append({"Error Message": "The API is showing None error code and error message"})
            else:
                error_code = ex.error_code
                error_msg = ex.message
                results.append({"Error Code": error_code, "Error Message": error_msg})

        return changed, results

    def rds_error_handler(self, ex, custom_msg):
        """    
        Generic method to handling errors
        :type ex: list
        :param ex: An exception object
        :type custom_msg: list
        :param custom_msg: List of custom error messages
        :return: retun error message
        """
        results = []
        error_code = ex.error_code
        error_msg = ex.message
        error_dict = {
            "FUWU_BIZ_COMMODITY_VERIFY_FAIL": "Provided db_engine, engine_version and"
                                              " db_instance_class is not valid or mismatch",
            "CreateOrder.Failed": "Provided db_engine, engine_version and"
                                  " db_instance_class is not valid or mismatch",
            "InvalidOrderTask.NotSupport": "Provided private_ip_address is not"
                                           " valid ip address for VPC under VSwitch"
        }

        if (ex.args is None) or (ex.args == "need more than 2 values to unpack") \
                or (error_msg == "need more than 2 values to unpack") or error_code is None:
            results.append({"Error Message": custom_msg[0]})
        elif error_code in error_dict:
            results.append(custom_msg[1] + str(error_dict[error_code]))
        else:
            results.append(custom_msg[2] + error_code +
                           " and message: " + error_msg)
        return results

