<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# this file is part of 'RAX-AutoScaler'
#
# Copyright 2014 Rackspace US, Inc.
#
# 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 common
import pyrax
import argparse
import time
import os
import sys
import logging.config
import random
from colouredconsolehandler import ColouredConsoleHandler
from auth import Auth
import cloudmonitor
import subprocess
from version import return_version


# CHECK logging.conf
logging_config = common.check_file('logging.conf')

if logging_config is None:
    logging.handlers.ColouredConsoleHandler = ColouredConsoleHandler
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)

else:

    logging_conf_file = logging_config
    logging.handlers.ColouredConsoleHandler = ColouredConsoleHandler
    logging.config.fileConfig(logging_conf_file)
    logger = logging.getLogger(__name__)


def exit_with_error(msg):
    """This function prints error message and exit with error.

    :param msg: error message
    :type name: str
    :returns: 1 (int) -- the return code

    """
    if msg is None:
        try:
            log_file = logger.root.handlers[0].baseFilename
            logger.info('completed with an error: %s' % log_file)
        except:
            print ('(info) rax-autoscale completed with an error')
    else:
        try:
            logger.error(msg)
            log_file = logger.root.handlers[0].baseFilename
            logger.info('completed with an error: %s' % log_file)
        except:
            print ('(error) %s' % msg)
            print ('(info) rax-autoscale completed with an error')

    exit(1)


def is_node_master(scalingGroup):
    """This function checks scaling group state and determines if node is a master.

    :param scalingGroup: data about servers in scaling group retrieve from
                         cloudmonitor
    :returns: 1 : if cluster state is unknown
              2 : node is a master
              3 : node is not a master

    """
    masters = []
    systemUUID = None
    systemUUID_file = None
    node_id = None
    logger.debug('executing dmidecode to get system uuid')
    try:
        sysout = subprocess.Popen(['dmidecode', '-s', 'system-uuid'],
                                  stdout=subprocess.PIPE)
        systemUUID = sysout.communicate()[0]
        systemUUID = systemUUID.strip()
    except Exception, e:
        logger.debug('Failed to execute dmidecode: %s' % str(e))
        pass

    if systemUUID is not None:
        logger.debug("Checking if file '%s' exists " % systemUUID)
        systemUUID_file = common.check_file(systemUUID)
        if systemUUID_file is not None:
            logger.debug("Reading file '%s' to get node id" % systemUUID_file)
            try:
                rfh = open(systemUUID_file, 'r').read()
                node_id = rfh.strip()
                logger.info("Using machine uuid available in '%s'"
                            % systemUUID_file)
            except:
                logger.warning("Unable to read a file '%s' in '%s'"
                               % (systemUUID, '/etc/rax-autoscaler'))
                pass

    if node_id is None:
        logger.info('Launching xenstore query to get machine uuid')
        node_id = common.get_machine_uuid()

        if systemUUID_file is None and systemUUID is not None:
            logger.debug("Creating file '%s' to save node id"
                         % systemUUID_file)
            try:
                newFile = '/etc/rax-autoscaler/' + systemUUID
                wfh = open(newFile, 'w')
                wfh.write(node_id)
                wfh.close()
            except Exception, e:
                logger.warning("Unable to create a file '%s': '%s'"
                               % (systemUUID, str(e)))
                pass

    sg_state = scalingGroup.get_state()
    if len(sg_state['active']) == 1:
        masters.append(sg_state['active'][0])
    elif len(sg_state['active']) > 1:
        masters.append(sg_state['active'][0])
        masters.append(sg_state['active'][1])
    else:
        logger.error('Unknown cluster state')
        return 1

    if node_id in masters:
        logger.info('Node is a master, continuing')
        return 2
    else:
        logger.info('Node is not a master, nothing to do. Exiting')
        return


def get_scaling_group(group, config_data):
    """This function checks and gets active servers in scaling group

    :param group: group name
    :param config_data: json configuration data
    :returns: scalingGroup if server state is active else null

    """
    group_id = common.get_group_value(config_data, group, 'group_id')
    if group_id is None:
        logger.error('Unable to get group_id from json file')
        return

    scalingGroup = cloudmonitor.scaling_group_servers(group_id)
    if scalingGroup is None:
        return
    # Check active server(s) in scaling group
    if len(scalingGroup.get_state()['active']) == 0:
        return
    else:
        logger.info('Server(s) in scaling group: %s' %
                    ', '.join(['(%s, %s)'
                              % (cloudmonitor.get_server_name(s_id), s_id)
                              for s_id in scalingGroup.get_state()['active']]))
    logger.info('Current Active Servers: ' +
                str(scalingGroup.get_state()['active_capacity']))
    return scalingGroup


def autoscale(group, config_data, args):
    """This function executes scale up or scale down policy

    :param group: group name
    :param config_data: json configuration data
    :param args: user provided arguments

    """
    au = pyrax.autoscale

    scalingGroup = get_scaling_group(group, config_data)
    if scalingGroup is None:
        return 1
    check_type = common.get_group_value(config_data, group, 'check_type')
    if check_type is None:
        return 1
    check_config = common.get_group_value(config_data, group, 'check_config')
    if check_config is None:
        return 1

    for s_id in scalingGroup.get_state()['active']:
        rv = cloudmonitor.add_cm_check(s_id, check_type, check_config)

    logger.info('Cluster Mode Enabled: %s' % str(args['cluster']))

    if args['cluster']:
        rv = is_node_master(scalingGroup)
        if rv is None:
            # Not a master, no need to proceed further
            return
        if rv == 1:
            # Cluster state unknown return error.
            return 1

    # Gather cluster statistics
    check_type = common.get_group_value(config_data, group, 'check_type')
    if check_type is None:
        check_type = 'agent.load_average'

    metric_name = common.get_group_value(config_data, group, 'metric_name')
    if metric_name is None:
        metric_name = '1m'

    logger.info('Gathering Monitoring Data')

    results = []
    cm = pyrax.cloud_monitoring
    # Get all CloudMonitoring entities on the account
    entities = cm.list_entities()

    # Shuffle entities so the sample uses different servers
    entities = random.sample(entities, len(entities))

    for ent in entities:
        # Check if the entity is also in the scaling group
        if ent.agent_id in scalingGroup.get_state()['active']:
            ent_checks = ent.list_checks()
            # Loop through checks to find checks of the correct type
            for check in ent_checks:
                if check.type == check_type:
                    data = check.get_metric_data_points(metric_name,
                                                        int(time.time())-300,
                                                        int(time.time()),
                                                        points=2)
                    if len(data) > 0:
                        point = len(data)-1
                        logger.info('Found metric for: ' + ent.name +
                                    ', value: ' + str(data[point]['average']))
                        results.append(float(data[point]['average']))
                        break

        # Restrict number of data points to save on API calls
        if len(results) >= args['max_sample']:
            logger.info('--max-sample value of ' + str(args['max_sample']) +
                        ' reached, not gathering any more statistics')
            break

    if len(results) == 0:
        logger.error('No data available')
        return 1
    else:
        average = sum(results)/len(results)
        scale_up_threshold = common.get_group_value(config_data, group,
                                                    'scale_up_threshold')
        if scale_up_threshold is None:
            scale_up_threshold = 0.6

    scale_down_threshold = common.get_group_value(config_data, group,
                                                  'scale_down_threshold')
    if scale_down_threshold is None:
        scale_down_threshold = 0.4

    logger.info('Cluster average for ' + check_type +
                '(' + metric_name + ') at: ' + str(average))

    if average > scale_up_threshold:
        try:
            logger.info('Above Threshold - Scaling Up')
            scale_policy_id = common.get_group_value(config_data, group,
                                                     'scale_up_policy')
            scale_policy = scalingGroup.get_policy(scale_policy_id)
            if not args['dry_run']:
                common.webhook_call(config_data, group, 'scale_up', 'pre')
                scale_policy.execute()
                common.webhook_call(config_data, group, 'scale_up', 'post')
            else:
                logger.info('Scale up prevented by --dry-run')
                logger.info('Scale up policy executed ('
                            + scale_policy_id + ')')
        except Exception, e:
            logger.warning('Scale up: %s' % str(e))
    elif average < scale_down_threshold:
        try:
            logger.info('Below Threshold - Scaling Down')
            scale_policy_id = common.get_group_value(config_data, group,
                                                     'scale_down_policy')
            scale_policy = scalingGroup.get_policy(scale_policy_id)
            if not args['dry_run']:
                common.webhook_call(config_data, group, 'scale_down', 'pre')
                scale_policy.execute()
                common.webhook_call(config_data, group, 'scale_down', 'post')
            else:
                logger.info('Scale down prevented by --dry-run')
                logger.info('Scale down policy executed (' +
                            scale_policy_id + ')')

        except Exception, e:
            logger.warning('Scale down: %s' % str(e))

    else:
        logger.info('Cluster within target paramters')


def parse_args():
    """This function validates user arguments and data in configuration file.

    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--as-group', required=False,
                        help='The autoscale group config ID')
    parser.add_argument('--os-username', required=False,
                        help='Rackspace Cloud user name')
    parser.add_argument('--os-password', required=False,
                        help='Rackspace Cloud account API key')
    parser.add_argument('--config-file', required=False, default='config.json',
                        help='The autoscale configuration .ini file'
                             '(default: config.json)'),
    parser.add_argument('--os-region-name', required=False,
                        help='The region to build the servers',
                        choices=['SYD', 'HKG', 'DFW', 'ORD', 'IAD', 'LON'])
    parser.add_argument('--cluster', required=False,
                        default=False, action='store_true')
    parser.add_argument('--version', action='version',
                        help='Show version number', version=return_version())
    parser.add_argument('--dry-run', required=False, default=False,
                        action='store_true',
                        help='Do not actually perform any scaling operations '
                             'or call webhooks')
    parser.add_argument('--max-sample', required=False, default=10, type=int,
                        help='Maximum number of servers to obtain monitoring '
                             'samples from')
    args = vars(parser.parse_args())

    return args


def main():
    """This function calls auth class for authentication and autoscale to
       execute scaling policy

    """

    args = parse_args()

    # CONFIG.ini
    config_file = common.check_file(args['config_file'])
    if config_file is None:
        exit_with_error("Either file is missing or is not readable: '%s'"
                        % args['config_file'])

    # Show Version
    logger.info(return_version())

    for arg in args:
        logger.debug('argument provided by user ' + arg + ' : ' +
                     str(args[arg]))

    # Get data from config.json
    config_data = common.get_config(config_file)
    if config_data is None:
        exit_with_error('Failed to read config file: ' + config_file)

    # Get group
    if not args['as_group']:
        if len(config_data['autoscale_groups'].keys()) == 1:
            as_group = config_data['autoscale_groups'].keys()[0]
        else:
            logger.debug("Getting system hostname")
            try:
                sysout = subprocess.Popen(['hostname'], stdout=subprocess.PIPE)
                hostname = (sysout.communicate()[0]).strip()
                if '-' in hostname:
                    hostname = hostname.rsplit('-', 1)[0]

                group_value = config_data["autoscale_groups"][hostname]
                as_group = hostname
            except Exception, e:
                logger.debug("Failed to get hostname: %s" % str(e))
                logger.warning("Multiple group found in config file, "
                               "please use 'as-group' option")
                exit_with_error('Unable to identify targeted group')
    else:
        try:
            group_value = config_data["autoscale_groups"][args['as_group']]
            as_group = args['as_group']
        except:
            exit_with_error("Unable to find group '" + args['as_group'] +
                            "' in " + config_file)

    username = common.get_user_value(args, config_data, 'os_username')
    if username is None:
        exit_with_error(None)
    api_key = common.get_user_value(args, config_data, 'os_password')
    if api_key is None:
        exit_with_error(None)
    region = common.get_user_value(args, config_data, 'os_region_name')
    if region is None:
        exit_with_error(None)

    session = Auth(username, api_key, region)

    if session.authenticate() is True:
        rv = autoscale(as_group, config_data, args)
        if rv is None:
            log_file = None
            if hasattr(logger.root.handlers[0], 'baseFilename'):
                log_file = logger.root.handlers[0].baseFilename
            if log_file is None:
                logger.info('completed successfull')
            else:
                logger.info('completed successfully: %s' % log_file)
        else:
            exit_with_error(None)
    else:
        exit_with_error('Authentication failed')


if __name__ == '__main__':
    main()
<commit_msg>Bug: #83 update documentation to address latest code update.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# this file is part of 'RAX-AutoScaler'
#
# Copyright 2014 Rackspace US, Inc.
#
# 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 common
import pyrax
import argparse
import time
import os
import sys
import logging.config
import random
from colouredconsolehandler import ColouredConsoleHandler
from auth import Auth
import cloudmonitor
import subprocess
from version import return_version


# CHECK logging.conf
logging_config = common.check_file('logging.conf')

if logging_config is None:
    logging.handlers.ColouredConsoleHandler = ColouredConsoleHandler
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)

else:

    logging_conf_file = logging_config
    logging.handlers.ColouredConsoleHandler = ColouredConsoleHandler
    logging.config.fileConfig(logging_conf_file)
    logger = logging.getLogger(__name__)


def exit_with_error(msg):
    """This function prints error message and exit with error.

    :param msg: error message
    :type name: str
    :returns: 1 (int) -- the return code

    """
    if msg is None:
        try:
            log_file = logger.root.handlers[0].baseFilename
            logger.info('completed with an error: %s' % log_file)
        except:
            print ('(info) rax-autoscale completed with an error')
    else:
        try:
            logger.error(msg)
            log_file = logger.root.handlers[0].baseFilename
            logger.info('completed with an error: %s' % log_file)
        except:
            print ('(error) %s' % msg)
            print ('(info) rax-autoscale completed with an error')

    exit(1)


def is_node_master(scalingGroup):
    """This function checks scaling group state and determines if node is a master.

    :param scalingGroup: data about servers in scaling group retrieve from
                         cloudmonitor
    :returns: 1     : if cluster state is unknown
              2     : node is a master
              None  : node is not a master

    """
    masters = []
    systemUUID = None
    systemUUID_file = None
    node_id = None
    logger.debug('executing dmidecode to get system uuid')
    try:
        sysout = subprocess.Popen(['dmidecode', '-s', 'system-uuid'],
                                  stdout=subprocess.PIPE)
        systemUUID = sysout.communicate()[0]
        systemUUID = systemUUID.strip()
    except Exception, e:
        logger.debug('Failed to execute dmidecode: %s' % str(e))
        pass

    if systemUUID is not None:
        logger.debug("Checking if file '%s' exists " % systemUUID)
        systemUUID_file = common.check_file(systemUUID)
        if systemUUID_file is not None:
            logger.debug("Reading file '%s' to get node id" % systemUUID_file)
            try:
                rfh = open(systemUUID_file, 'r').read()
                node_id = rfh.strip()
                logger.info("Using machine uuid available in '%s'"
                            % systemUUID_file)
            except:
                logger.warning("Unable to read a file '%s' in '%s'"
                               % (systemUUID, '/etc/rax-autoscaler'))
                pass

    if node_id is None:
        logger.info('Launching xenstore query to get machine uuid')
        node_id = common.get_machine_uuid()

        if systemUUID_file is None and systemUUID is not None:
            logger.debug("Creating file '%s' to save node id"
                         % systemUUID_file)
            try:
                newFile = '/etc/rax-autoscaler/' + systemUUID
                wfh = open(newFile, 'w')
                wfh.write(node_id)
                wfh.close()
            except Exception, e:
                logger.warning("Unable to create a file '%s': '%s'"
                               % (systemUUID, str(e)))
                pass

    sg_state = scalingGroup.get_state()
    if len(sg_state['active']) == 1:
        masters.append(sg_state['active'][0])
    elif len(sg_state['active']) > 1:
        masters.append(sg_state['active'][0])
        masters.append(sg_state['active'][1])
    else:
        logger.error('Unknown cluster state')
        return 1

    if node_id in masters:
        logger.info('Node is a master, continuing')
        return 2
    else:
        logger.info('Node is not a master, nothing to do. Exiting')
        return


def get_scaling_group(group, config_data):
    """This function checks and gets active servers in scaling group

    :param group: group name
    :param config_data: json configuration data
    :returns: scalingGroup if server state is active else null

    """
    group_id = common.get_group_value(config_data, group, 'group_id')
    if group_id is None:
        logger.error('Unable to get group_id from json file')
        return

    scalingGroup = cloudmonitor.scaling_group_servers(group_id)
    if scalingGroup is None:
        return
    # Check active server(s) in scaling group
    if len(scalingGroup.get_state()['active']) == 0:
        return
    else:
        logger.info('Server(s) in scaling group: %s' %
                    ', '.join(['(%s, %s)'
                              % (cloudmonitor.get_server_name(s_id), s_id)
                              for s_id in scalingGroup.get_state()['active']]))
    logger.info('Current Active Servers: ' +
                str(scalingGroup.get_state()['active_capacity']))
    return scalingGroup


def autoscale(group, config_data, args):
    """This function executes scale up or scale down policy

    :param group: group name
    :param config_data: json configuration data
    :param args: user provided arguments

    """
    au = pyrax.autoscale

    scalingGroup = get_scaling_group(group, config_data)
    if scalingGroup is None:
        return 1
    check_type = common.get_group_value(config_data, group, 'check_type')
    if check_type is None:
        return 1
    check_config = common.get_group_value(config_data, group, 'check_config')
    if check_config is None:
        return 1

    for s_id in scalingGroup.get_state()['active']:
        rv = cloudmonitor.add_cm_check(s_id, check_type, check_config)

    logger.info('Cluster Mode Enabled: %s' % str(args['cluster']))

    if args['cluster']:
        rv = is_node_master(scalingGroup)
        if rv is None:
            # Not a master, no need to proceed further
            return
        if rv == 1:
            # Cluster state unknown return error.
            return 1

    # Gather cluster statistics
    check_type = common.get_group_value(config_data, group, 'check_type')
    if check_type is None:
        check_type = 'agent.load_average'

    metric_name = common.get_group_value(config_data, group, 'metric_name')
    if metric_name is None:
        metric_name = '1m'

    logger.info('Gathering Monitoring Data')

    results = []
    cm = pyrax.cloud_monitoring
    # Get all CloudMonitoring entities on the account
    entities = cm.list_entities()

    # Shuffle entities so the sample uses different servers
    entities = random.sample(entities, len(entities))

    for ent in entities:
        # Check if the entity is also in the scaling group
        if ent.agent_id in scalingGroup.get_state()['active']:
            ent_checks = ent.list_checks()
            # Loop through checks to find checks of the correct type
            for check in ent_checks:
                if check.type == check_type:
                    data = check.get_metric_data_points(metric_name,
                                                        int(time.time())-300,
                                                        int(time.time()),
                                                        points=2)
                    if len(data) > 0:
                        point = len(data)-1
                        logger.info('Found metric for: ' + ent.name +
                                    ', value: ' + str(data[point]['average']))
                        results.append(float(data[point]['average']))
                        break

        # Restrict number of data points to save on API calls
        if len(results) >= args['max_sample']:
            logger.info('--max-sample value of ' + str(args['max_sample']) +
                        ' reached, not gathering any more statistics')
            break

    if len(results) == 0:
        logger.error('No data available')
        return 1
    else:
        average = sum(results)/len(results)
        scale_up_threshold = common.get_group_value(config_data, group,
                                                    'scale_up_threshold')
        if scale_up_threshold is None:
            scale_up_threshold = 0.6

    scale_down_threshold = common.get_group_value(config_data, group,
                                                  'scale_down_threshold')
    if scale_down_threshold is None:
        scale_down_threshold = 0.4

    logger.info('Cluster average for ' + check_type +
                '(' + metric_name + ') at: ' + str(average))

    if average > scale_up_threshold:
        try:
            logger.info('Above Threshold - Scaling Up')
            scale_policy_id = common.get_group_value(config_data, group,
                                                     'scale_up_policy')
            scale_policy = scalingGroup.get_policy(scale_policy_id)
            if not args['dry_run']:
                common.webhook_call(config_data, group, 'scale_up', 'pre')
                scale_policy.execute()
                common.webhook_call(config_data, group, 'scale_up', 'post')
            else:
                logger.info('Scale up prevented by --dry-run')
                logger.info('Scale up policy executed ('
                            + scale_policy_id + ')')
        except Exception, e:
            logger.warning('Scale up: %s' % str(e))
    elif average < scale_down_threshold:
        try:
            logger.info('Below Threshold - Scaling Down')
            scale_policy_id = common.get_group_value(config_data, group,
                                                     'scale_down_policy')
            scale_policy = scalingGroup.get_policy(scale_policy_id)
            if not args['dry_run']:
                common.webhook_call(config_data, group, 'scale_down', 'pre')
                scale_policy.execute()
                common.webhook_call(config_data, group, 'scale_down', 'post')
            else:
                logger.info('Scale down prevented by --dry-run')
                logger.info('Scale down policy executed (' +
                            scale_policy_id + ')')

        except Exception, e:
            logger.warning('Scale down: %s' % str(e))

    else:
        logger.info('Cluster within target paramters')


def parse_args():
    """This function validates user arguments and data in configuration file.

    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--as-group', required=False,
                        help='The autoscale group config ID')
    parser.add_argument('--os-username', required=False,
                        help='Rackspace Cloud user name')
    parser.add_argument('--os-password', required=False,
                        help='Rackspace Cloud account API key')
    parser.add_argument('--config-file', required=False, default='config.json',
                        help='The autoscale configuration .ini file'
                             '(default: config.json)'),
    parser.add_argument('--os-region-name', required=False,
                        help='The region to build the servers',
                        choices=['SYD', 'HKG', 'DFW', 'ORD', 'IAD', 'LON'])
    parser.add_argument('--cluster', required=False,
                        default=False, action='store_true')
    parser.add_argument('--version', action='version',
                        help='Show version number', version=return_version())
    parser.add_argument('--dry-run', required=False, default=False,
                        action='store_true',
                        help='Do not actually perform any scaling operations '
                             'or call webhooks')
    parser.add_argument('--max-sample', required=False, default=10, type=int,
                        help='Maximum number of servers to obtain monitoring '
                             'samples from')
    args = vars(parser.parse_args())

    return args


def main():
    """This function calls auth class for authentication and autoscale to
       execute scaling policy

    """

    args = parse_args()

    # CONFIG.ini
    config_file = common.check_file(args['config_file'])
    if config_file is None:
        exit_with_error("Either file is missing or is not readable: '%s'"
                        % args['config_file'])

    # Show Version
    logger.info(return_version())

    for arg in args:
        logger.debug('argument provided by user ' + arg + ' : ' +
                     str(args[arg]))

    # Get data from config.json
    config_data = common.get_config(config_file)
    if config_data is None:
        exit_with_error('Failed to read config file: ' + config_file)

    # Get group
    if not args['as_group']:
        if len(config_data['autoscale_groups'].keys()) == 1:
            as_group = config_data['autoscale_groups'].keys()[0]
        else:
            logger.debug("Getting system hostname")
            try:
                sysout = subprocess.Popen(['hostname'], stdout=subprocess.PIPE)
                hostname = (sysout.communicate()[0]).strip()
                if '-' in hostname:
                    hostname = hostname.rsplit('-', 1)[0]

                group_value = config_data["autoscale_groups"][hostname]
                as_group = hostname
            except Exception, e:
                logger.debug("Failed to get hostname: %s" % str(e))
                logger.warning("Multiple group found in config file, "
                               "please use 'as-group' option")
                exit_with_error('Unable to identify targeted group')
    else:
        try:
            group_value = config_data["autoscale_groups"][args['as_group']]
            as_group = args['as_group']
        except:
            exit_with_error("Unable to find group '" + args['as_group'] +
                            "' in " + config_file)

    username = common.get_user_value(args, config_data, 'os_username')
    if username is None:
        exit_with_error(None)
    api_key = common.get_user_value(args, config_data, 'os_password')
    if api_key is None:
        exit_with_error(None)
    region = common.get_user_value(args, config_data, 'os_region_name')
    if region is None:
        exit_with_error(None)

    session = Auth(username, api_key, region)

    if session.authenticate() is True:
        rv = autoscale(as_group, config_data, args)
        if rv is None:
            log_file = None
            if hasattr(logger.root.handlers[0], 'baseFilename'):
                log_file = logger.root.handlers[0].baseFilename
            if log_file is None:
                logger.info('completed successfull')
            else:
                logger.info('completed successfully: %s' % log_file)
        else:
            exit_with_error(None)
    else:
        exit_with_error('Authentication failed')


if __name__ == '__main__':
    main()

{'fraction_non_alphanumeric': 0.09678637751408517, 'fraction_numerical': 0.0034937521639230745, 'mean_word_length': 2.0285006195786863, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3173403', 'n_tokens_mistral': 8740, 'n_tokens_neox': 8413, 'n_words': 2366}