Source code for stalker.models.schedulers

# -*- coding: utf-8 -*-
# Stalker a Production Asset Management System
# Copyright (C) 2009-2013 Erkan Ozgur Yilmaz
# 
# This file is part of Stalker.
# 
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

import os
import subprocess
import tempfile
import datetime
import csv

import stalker
from stalker import log, Entity
from stalker import defaults

import logging
logger = logging.getLogger(__name__)
logger.setLevel(log.logging_level)

[docs]class SchedulerBase(object): """This is the base class for schedulers. All the schedulers should be derived from this class. """
[docs] def __init__(self, studio=None): self._studio = None self.studio = studio
def _validate_studio(self, studio_in): """validates the given studio_in value """ if studio_in is not None: from stalker import Studio if not isinstance(studio_in, Studio): raise TypeError('%s.studio should be an instance of ' 'stalker.models.studio.Studio, not %s' % (self.__class__.__name__, studio_in.__class__.__name__)) return studio_in @property def studio(self): """studio getter """ return self._studio @studio.setter
[docs] def studio(self, studio_in): """studio setter """ self._studio = self._validate_studio(studio_in)
[docs] def schedule(self): """the main scheduling function should be implemented in the derivatives """ raise NotImplementedError
[docs]class TaskJugglerScheduler(SchedulerBase): """This is the main scheduler for Stalker right now. Integrates Stalker and TaskJuggler together by using TaskJugglerScheduler to solve the scheduling problem. TaskJugglerScheduler needs a :class:`~stalker.models.studio.Studio` instance to work with. TJS will create a .tjp file and then solve the tasks and restore the computed_start and computed_end dates. Combining all the Projects in one tjp file has a very nice side effect, projects using the same resources will respect their allocations to the resource. """
[docs] def __init__(self, studio=None): super(TaskJugglerScheduler, self).__init__(studio) self.tjp_content = '' self.temp_file_full_path = None self.temp_file_path = None self.temp_file_name = None self.tjp_file_full_path = None self.tjp_file = None self.csv_file_full_path = None self.csv_file = None
def _create_tjp_file(self): """creates the tjp file """ self.temp_file_full_path = tempfile.mktemp(prefix='Stalker_') self.temp_file_name = os.path.basename(self.temp_file_full_path) self.tjp_file_full_path = self.temp_file_full_path + ".tjp" self.csv_file_full_path = self.temp_file_full_path + ".csv" def _create_tjp_file_content(self): """creates the tjp file content """ from jinja2 import Template template = Template(defaults.tjp_main_template) self.tjp_content = template.render({ 'stalker': stalker, 'studio': self.studio, 'csv_file_name': self.temp_file_name, 'csv_file_full_path': self.temp_file_full_path }) def _fill_tjp_file(self): """fills the tjp file with content """ with open(self.tjp_file_full_path, 'w+') as self.tjp_file: self.tjp_file.write(self.tjp_content) def _delete_tjp_file(self): """deletes the temp tjp file """ try: os.remove(self.tjp_file_full_path) except OSError: pass def _delete_csv_file(self): """deletes the temp csv file """ try: os.remove(self.csv_file_full_path) except OSError: pass def _clean_up(self): """removes the temp files """ self._delete_tjp_file() self._delete_csv_file() def _parse_csv_file(self): """parses back the csv file and fills the tasks with computes_start and computed end values """ logger.debug('csv_file_full_path : %s' % self.csv_file_full_path) import transaction with open(self.csv_file_full_path, 'r') as self.csv_file: csv_content = csv.reader(self.csv_file, delimiter=';') lines = [line for line in csv_content] lines.pop(0) for data in lines: id_line = data[0] entity_id = int(id_line.split('.')[-1].split('_')[-1]) entity = Entity.query.filter(Entity.id==entity_id).first() if entity: start_date = datetime.datetime.strptime(data[1], "%Y-%m-%d-%H:%M") end_date = datetime.datetime.strptime(data[2], "%Y-%m-%d-%H:%M") entity.computed_start = start_date entity.computed_end = end_date transaction.commit()
[docs] def schedule(self): """does the scheduling """ # check the studio attribute from stalker import Studio if not isinstance(self.studio, Studio): raise TypeError('%s.studio should be an instance of ' 'stalker.models.studio.Studio, not %s' % (self.__class__.__name__, self.studio.__class__.__name__)) # ******************************************************************** # Adjust Studio.start and Studio.end # get the active projects # get root tasks # calculate the earliest start and the latest end self.studio._start = datetime.datetime(2013, 1, 1) self.studio._end = datetime.datetime(2016, 1, 1) #for project in self.studio.active_projects: # for root_task in project.root_tasks: # if root_task.start < self.studio.start: # self.studio.start = root_task.start # if root_task.end > self.studio.end: # self.studio.end = root_task.end # now for safety multiply the duration by 2 #self.studio.end = (self.studio.end - self.studio.start) * 5 + \ # self.studio.start # create a tjp file self._create_tjp_file() # create tjp file content self._create_tjp_file_content() # fill it with data self._fill_tjp_file() logger.debug('tjp_file_full_path: %s' % self.tjp_file_full_path) # pass it to tj3 proc = subprocess.Popen( [defaults.tj_command, self.tjp_file_full_path], stderr=subprocess.PIPE ) # wait it to complete proc.wait() if proc.returncode: raise RuntimeError(proc.stderr.readlines()) logger.debug('tj3 return code: %s' % proc.returncode) logger.debug('tj3 output: %s' % proc.stderr.readlines()) # read back the csv file self._parse_csv_file() # remove the tjp file #self._clean_up()