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

# ======================================================================
# Copyright 2016 <NAME>
# 
# 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 time

from supervisor.childutils import get_asctime
from supervisor.states import ProcessStates

from supervisors.strategy import get_address
from supervisors.ttypes import DeploymentStrategies
from supervisors.utils import supervisors_short_cuts


class Commander(object):
    """ Base class handling the starting / stopping of processes and applications.
    Attributes are:
    - planned_sequence: the applications to be commanded, as a dictionary of processes,
        grouped by application sequence order, application name and process sequence order,
    - planned_jobs: the current sequence of applications to be commanded,
        as a dictionary of processes, grouped by application name and process sequence order,
    - current_jobs: a dictionary of commanded processes, grouped by application name. """

    def __init__(self, supervisors):
        """ Initialization of the attributes. """
        # keep a reference of the Supervisors data
        self.supervisors = supervisors
        # shortcuts for readability
        supervisors_short_cuts(self, ['logger'])
        #attributes
        self.planned_sequence = {} # {application_sequence: {application_name: {process_sequence: [process]}}}
        self.planned_jobs = {} # {application_name: {process_sequence: [process]}}
        self.current_jobs = {} # {application_name: [process]}

    def in_progress(self):
        """ Return True if there are jobs planned or in progress. """
        self.logger.debug('progress: planned_sequence={} planned_jobs={} current_jobs={}'.format(
            self.printable_planned_sequence(), self.printable_planned_jobs(), self.printable_current_jobs()))
        return len(self.planned_sequence) or len(self.planned_jobs) or len(self.current_jobs)

    # log facilities
    def printable_planned_sequence(self):
        return {application_sequence:
                {application_name:
                    {sequence: self.printable_process_list(processes) for sequence, processes in sequences.items()}
                for application_name, sequences in applications.items()}
            for application_sequence, applications in self.planned_sequence.items()}

    def printable_planned_jobs(self):
        return {application_name:
                {sequence: self.printable_process_list(processes) for sequence, processes in sequences.items()}
            for application_name, sequences in self.planned_jobs.items()}

    def printable_current_jobs(self):
        return {application_name: self.printable_process_list(processes) for application_name, processes in self.current_jobs.items()}

    def printable_process_list(self, processes):
        return [process.namespec() for process in processes]

    def initial_jobs(self):
        self.logger.info('planned_sequence={}'.format(self.printable_planned_sequence()))
        # pop lower application group from planned_sequence
        if self.planned_sequence:
            self.planned_jobs = self.planned_sequence.pop(min(self.planned_sequence.keys()))
            self.logger.info('planned_jobs={}'.format(self.printable_planned_jobs()))
            # iterate on copy to avoid problems with deletions
            for application_name in self.planned_jobs.keys()[:]:
                self.process_application_jobs(application_name)
        else:
            self.logger.info('command completed')

    def process_application_jobs(self, application_name):
        if application_name in self.planned_jobs:
            sequence = self.planned_jobs[application_name]
            self.current_jobs[application_name] = jobs = []
            # loop until there is something to do in sequence
            while sequence and not jobs and application_name in self.planned_jobs:
                # pop lower group from sequence
                group = sequence.pop(min(sequence.keys()))
                self.logger.info('application {} - next group: {}'.format(application_name, self.printable_process_list(group)))
                for process in group:
                    self.logger.trace('{} - state={}'.format(process.namespec(), process.state_string()))
                    self.process_job(process, jobs)
            # if nothing in progress when exiting the loop, delete application entry in current_jobs
            if not jobs:
                self.current_jobs.pop(application_name, None)
            # clean application job if its sequence is empty
            if not sequence:
                self.logger.info('all jobs planned for application {}'.format(application_name))
                self.planned_jobs.pop(application_name, None)
        else:
            self.logger.warn('application {} not found in jobs'.format(application_name))


class Starter(Commander):
    """ Class handling the starting of processes and applications.
    Attributes are:
    - strategy: the deployment strategy applied, defaulted to the value set in the Supervisor configuration file, """

    def __init__(self, supervisors):
        """ Initialization of the attributes. """
        Commander.__init__(self, supervisors)
        #attributes
        self._strategy = supervisors.options.deployment_strategy

    @property
    def strategy(self):
        """ Property for the 'strategy' attribute.
        The setter is used to overload the default strategy (used in rpcinterface and web page). """
        return self._strategy

    @strategy.setter
    def strategy(self, strategy):
        self.logger.info('start applications using strategy {}'.format(DeploymentStrategies._to_string(strategy)))
        self._strategy = strategy

    def start_applications(self, applications):
        """ Plan and start the necessary jobs to start all the applications having a start_sequence.
        It uses the default strategy, as defined in the Supervisor configuration file. """
        self.logger.info('start all applications')
        # internal call: default strategy always used
        self.strategy = self.supervisors.options.deployment_strategy
        # deployment initialization: push program list in todo list
        for application in applications:
            # do not deploy an application that is not properly STOPPED
            if application.stopped() and application.rules.start_sequence > 0:
                self.store_application_start_sequence(application)
        # start work
        self.initial_jobs()

    def start_application(self, strategy, application):
        """ Plan and start the necessary jobs to start the application in parameter, with the strategy requested. """
        self.logger.info('start application {}'.format(application.application_name))
        # called from rpcinterface: strategy is a user choice
        self.strategy = strategy
        # push program list in todo list and start work
        if application.stopped():
            self.store_application_start_sequence(application)
            self.logger.info('planned_sequence={}'.format(self.printable_planned_sequence()))
            # add application immediately to planned jobs
            self.planned_jobs.update(self.planned_sequence.pop(min(self.planned_sequence.keys())))
            self.process_application_jobs(application.application_name)
        # return True when deployment over
        return not self.in_progress()

    def start_process(self, strategy, process, extra_args=None):
        """ Plan and start the necessary job to start the process in parameter, with the strategy requested. """
        self.logger.info('start process {}'.format(process.namespec()))
        # called from rpcinterface: strategy is a user choice
        self.strategy = strategy
        # store extra arguments to be passed to the command line
        process.extra_args = extra_args
        # WARN: when deploying a single process (outside the scope of an application deployment), do NOT consider the 'wait_exit' rule
        process.ignore_wait_exit = True
        # push program list in todo list and start work
        job = self.current_jobs.setdefault(process.application_name, [])
        self.process_job(process, job)
        # upon failure, remove inProgress entry if empty
        if not job:
            del self.current_jobs[process.application_name]
        # return True when deployment over<commit_msg>fix exception with empty sequence<commit_after>#!/usr/bin/python
#-*- coding: utf-8 -*-

# ======================================================================
# Copyright 2016 <NAME>
# 
# 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 time

from supervisor.childutils import get_asctime
from supervisor.states import ProcessStates

from supervisors.strategy import get_address
from supervisors.ttypes import DeploymentStrategies
from supervisors.utils import supervisors_short_cuts


class Commander(object):
    """ Base class handling the starting / stopping of processes and applications.
    Attributes are:
    - planned_sequence: the applications to be commanded, as a dictionary of processes,
        grouped by application sequence order, application name and process sequence order,
    - planned_jobs: the current sequence of applications to be commanded,
        as a dictionary of processes, grouped by application name and process sequence order,
    - current_jobs: a dictionary of commanded processes, grouped by application name. """

    def __init__(self, supervisors):
        """ Initialization of the attributes. """
        # keep a reference of the Supervisors data
        self.supervisors = supervisors
        # shortcuts for readability
        supervisors_short_cuts(self, ['logger'])
        #attributes
        self.planned_sequence = {} # {application_sequence: {application_name: {process_sequence: [process]}}}
        self.planned_jobs = {} # {application_name: {process_sequence: [process]}}
        self.current_jobs = {} # {application_name: [process]}

    def in_progress(self):
        """ Return True if there are jobs planned or in progress. """
        self.logger.debug('progress: planned_sequence={} planned_jobs={} current_jobs={}'.format(
            self.printable_planned_sequence(), self.printable_planned_jobs(), self.printable_current_jobs()))
        return len(self.planned_sequence) or len(self.planned_jobs) or len(self.current_jobs)

    # log facilities
    def printable_planned_sequence(self):
        return {application_sequence:
                {application_name:
                    {sequence: self.printable_process_list(processes) for sequence, processes in sequences.items()}
                for application_name, sequences in applications.items()}
            for application_sequence, applications in self.planned_sequence.items()}

    def printable_planned_jobs(self):
        return {application_name:
                {sequence: self.printable_process_list(processes) for sequence, processes in sequences.items()}
            for application_name, sequences in self.planned_jobs.items()}

    def printable_current_jobs(self):
        return {application_name: self.printable_process_list(processes) for application_name, processes in self.current_jobs.items()}

    def printable_process_list(self, processes):
        return [process.namespec() for process in processes]

    def initial_jobs(self):
        self.logger.info('planned_sequence={}'.format(self.printable_planned_sequence()))
        # pop lower application group from planned_sequence
        if self.planned_sequence:
            self.planned_jobs = self.planned_sequence.pop(min(self.planned_sequence.keys()))
            self.logger.info('planned_jobs={}'.format(self.printable_planned_jobs()))
            # iterate on copy to avoid problems with deletions
            for application_name in self.planned_jobs.keys()[:]:
                self.process_application_jobs(application_name)
        else:
            self.logger.info('command completed')

    def process_application_jobs(self, application_name):
        if application_name in self.planned_jobs:
            sequence = self.planned_jobs[application_name]
            self.current_jobs[application_name] = jobs = []
            # loop until there is something to do in sequence
            while sequence and not jobs and application_name in self.planned_jobs:
                # pop lower group from sequence
                group = sequence.pop(min(sequence.keys()))
                self.logger.info('application {} - next group: {}'.format(application_name, self.printable_process_list(group)))
                for process in group:
                    self.logger.trace('{} - state={}'.format(process.namespec(), process.state_string()))
                    self.process_job(process, jobs)
            # if nothing in progress when exiting the loop, delete application entry in current_jobs
            if not jobs:
                self.current_jobs.pop(application_name, None)
            # clean application job if its sequence is empty
            if not sequence:
                self.logger.info('all jobs planned for application {}'.format(application_name))
                self.planned_jobs.pop(application_name, None)
        else:
            self.logger.warn('application {} not found in jobs'.format(application_name))


class Starter(Commander):
    """ Class handling the starting of processes and applications.
    Attributes are:
    - strategy: the deployment strategy applied, defaulted to the value set in the Supervisor configuration file, """

    def __init__(self, supervisors):
        """ Initialization of the attributes. """
        Commander.__init__(self, supervisors)
        #attributes
        self._strategy = supervisors.options.deployment_strategy

    @property
    def strategy(self):
        """ Property for the 'strategy' attribute.
        The setter is used to overload the default strategy (used in rpcinterface and web page). """
        return self._strategy

    @strategy.setter
    def strategy(self, strategy):
        self.logger.info('start applications using strategy {}'.format(DeploymentStrategies._to_string(strategy)))
        self._strategy = strategy

    def start_applications(self, applications):
        """ Plan and start the necessary jobs to start all the applications having a start_sequence.
        It uses the default strategy, as defined in the Supervisor configuration file. """
        self.logger.info('start all applications')
        # internal call: default strategy always used
        self.strategy = self.supervisors.options.deployment_strategy
        # deployment initialization: push program list in todo list
        for application in applications:
            # do not deploy an application that is not properly STOPPED
            if application.stopped() and application.rules.start_sequence > 0:
                self.store_application_start_sequence(application)
        # start work
        self.initial_jobs()

    def start_application(self, strategy, application):
        """ Plan and start the necessary jobs to start the application in parameter, with the strategy requested. """
        self.logger.info('start application {}'.format(application.application_name))
        # called from rpcinterface: strategy is a user choice
        self.strategy = strategy
        # push program list in todo list and start work
        if application.stopped():
            self.store_application_start_sequence(application)
            self.logger.info('planned_sequence={}'.format(self.printable_planned_sequence()))
            if self.planned_sequence:
                # add application immediately to planned jobs if something in list
                self.planned_jobs.update(self.planned_sequence.pop(min(self.planned_sequence.keys())))
                self.process_application_jobs(application.application_name)
        # return True when deployment over
        return not self.in_progress()

    def start_process(self, strategy, process, extra_args=None):
        """ Plan and start the necessary job to start the process in parameter, with the strategy requested. """
        self.logger.info('start process {}'.format(process.namespec()))
        # called from rpcinterface: strategy is a user choice
        self.strategy = strategy
        # store extra arguments to be passed to the command line
        process.extra_args = extra_args
        # WARN: when deploying a single process (outside the scope of an application deployment), do NOT consider the 'wait_exit' rule
        process.ignore_wait_exit = True
        # push program list in todo list and start work
        job = self.current_jobs.setdefault(process.application_name, [])
        self.process_job(process, job)
        # upon failure, remove inProgress entry if empty
        if not job:
            del self.current_jobs[process.application_name]
        # return True when deployment over
{'fraction_non_alphanumeric': 0.09406076589198396, 'fraction_numerical': 0.0010988407230371957, 'mean_word_length': 3.14624145785877, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7187622', 'n_tokens_mistral': 4536, 'n_tokens_neox': 4249, 'n_words': 1449}