diff --git a/almir/__init__.py b/almir/__init__.py index 2375a19..590745d 100644 --- a/almir/__init__.py +++ b/almir/__init__.py @@ -102,6 +102,11 @@ def main(global_config, **settings): renderer='json', request_method='GET') + config.add_route('modify_storage', '/storage/modify') + config.add_view('almir.views.modify_storage', + route_name='modify_storage', + request_method='POST') + # exception handling views config.add_view('almir.views.httpexception', context=HTTPError, diff --git a/almir/forms.py b/almir/forms.py index 197958c..917eb4e 100644 --- a/almir/forms.py +++ b/almir/forms.py @@ -6,6 +6,7 @@ job_states = ( ('finished+running', 'Finished + Running'), ('scheduled', 'Scheduled'), + ('disabled', 'Disabled'), ) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index a12f088..3724622 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -7,7 +7,11 @@ from contextlib import contextmanager from subprocess import Popen, PIPE -from almir.lib.utils import nl2br +#from almir.lib.utils import nl2br +from utils import nl2br + +import logging +log = logging.getLogger(__name__) CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) @@ -65,6 +69,21 @@ def from_temp_config(cls, name, address, port, password): def start_process(self): return Popen(shlex.split(self.bconsole_command), stdout=PIPE, stdin=PIPE, stderr=PIPE) + def send_command(self, cmd): + log.debug('Sending command to bconsole: %s' % cmd) + p = self.start_process() + stdout, stderr = p.communicate(cmd) + log.debug('Command output by bconsole:') + log.debug(stdout) + # cleaning stdout from connection info + # removing firsts four lines +# for i in range(3): +# stdout = stdout[:stdout.find('\n')] + + # removing you have messages. msg + stdout = stdout.replace('You have messages.\n','') + return stdout + def is_running(self): try: self.get_version() @@ -73,8 +92,8 @@ def is_running(self): return False def get_version(self): - p = self.start_process() - stdout, stderr = p.communicate('version\n') + stdout = self.send_command('version\n') + version = filter(lambda s: 'Version' in s, stdout.split('\n')) if version: return version[-1] @@ -82,8 +101,8 @@ def get_version(self): raise DirectorNotRunning def get_jobs_settings(self): - p = self.start_process() - stdout, stderr = p.communicate('show job') + stdout = self.send_command('show job\n') + jobs = [] for job in stdout.split('Job:'): jobs.append(JOBS_DEF_RE.find(stdout)) @@ -111,18 +130,43 @@ def make_backup(self, job, level=None, storage=None, fileset=None, client=None, if when: cmd += " when=%s" % when - p = self.start_process() - stdout, stderr = p.communicate(cmd + "\nyes\n") + stdout = self.send_command(cmd + "\nyes\n") + if True: return "jobid" else: # TODO: stderr why job failed? return False + def get_disabled_jobs(self): + # get the list of disabled jobs + stdout = self.send_command('show disabled\n') + +# example of header: +# Disabled Jobs: +# BackupCatalog +# + + + try: + unparsed_jobs = stdout.split('Disabled Jobs:\n')[1] + except IndexError: + return [] + + disabled=[x.strip() for x in unparsed_jobs.split('\n') if len(x)>1] + + jobs = [] + + for job in disabled: + jobs.append({'name': job, + }) + + return jobs + def get_upcoming_jobs(self, days=1): """""" - p = self.start_process() - stdout, stderr = p.communicate('.status dir scheduled days=%d\n' % days) + + stdout = self.send_command('.status dir scheduled days=%d\n' % days) #if stderr.strip(): # pass # TODO: display flash? @@ -133,6 +177,7 @@ def get_upcoming_jobs(self, days=1): return [] jobs = [] + for line in unparsed_jobs.split('\n'): if not line.strip(): continue @@ -149,6 +194,126 @@ def get_upcoming_jobs(self, days=1): return jobs + + def mount_storage(self, storage, slot=0): + """Mounts the volume contained in the slot *slot* on the storage *storage*""" + + cmd = 'mount=%s slot=%d\n' % (storage,slot) + stdout = self.send_command(cmd) + + is_ok = stdout.find('is mounted') + + return is_ok != -1 + + + def unmount_storage(self, storage): + """Unmounts the storage *storage*""" + cmd = 'unmount=%s \n' % storage + stdout = self.send_command(cmd) + + is_ok = stdout.find('unmounted') + + return is_ok != -1 + + + def release(self, storage): + """Releases the storage *storage*""" + cmd = 'release=%s \n' % storage + stdout = self.send_command(cmd) + + is_ok = stdout.find('released') + + return is_ok != -1 + + + def update_slots(self): + """Update slots""" + cmd = 'update slots\n' + stdout = self.send_command(cmd) + + is_ok = stdout.find('error') + + return is_ok == -1 + + + def delete(self, volume=None, jobid=None): + """Deletes an object""" + + if not volume and not jobid: + return False # what you want to delete? + + if volume: + cmd = ' volume=%s\nyes' % (volume) + if jobid: + cmd = ' jobid=%d ' % (jobid) + + cmd = 'delete %s \n' % cmd + + stdout = self.send_command(cmd) + + is_ok = stdout.find('deleted') + + return is_ok != -1 + + def create_label(self, pool, storage='', label = None, barcode = False ): + """Create a new label""" + + if not label and not barcode: + return False # we need or manual label or barcode + + cmd = 'label pool=%s storage=%s' % (pool,storage) + + if barcode: + cmd += ( " barcode\n" ) + else: + cmd += ( "\n%s\n" % label ) + + stdout = self.send_command(cmd) + + is_ok = stdout.find('successfully created') + + return is_ok != -1 + + + def enable_job(self, jobname ): + """Enables job named as passed by argument""" + + cmd = 'enable job=%s\n' % jobname + stdout = self.send_command(cmd) + + is_ok = stdout.find('enabled') + + return is_ok != -1 + + + def disable_job(self, jobname): + """Disables job named as passed by argument""" + + cmd = 'disable job=%s\n' % jobname + stdout = self.send_command(cmd) + + is_ok = stdout.find('disabled') + + return is_ok != -1 + + + def estimate_job(self, jobname ): + """Estimates a job returns -1,-1 if something goes wrong""" + + cmd = 'estimate job=%s\n' % jobname + stdout = self.send_command(cmd) + + try: + retcode, files, bytes = re.findall('\\d+',stdout.replace(',','')) + except ValueError: + retcode = -1 + + if int(retcode) != 2000: + return -1,-1 + else: + return int(files), int(bytes) + + def send_command_by_polling(self, command, process=None): """""" if command == 'quit': @@ -185,3 +350,4 @@ def send_command_by_polling(self, command, process=None): output = nl2br(output) return process, {"commands": [output]} + diff --git a/almir/models.py b/almir/models.py index 523f5de..4164ee2 100644 --- a/almir/models.py +++ b/almir/models.py @@ -227,6 +227,10 @@ def get_list(cls, **kw): if appstruct and appstruct['state'] == 'scheduled': return BConsole().get_upcoming_jobs() + + if appstruct and appstruct['state'] == 'disabled': + return BConsole().get_disabled_jobs() + query = cls.query.options(joinedload(cls.status), joinedload(cls.client)) if appstruct: if appstruct['status']: diff --git a/almir/static/js/main.js b/almir/static/js/main.js index 9c40d93..6dabef7 100644 --- a/almir/static/js/main.js +++ b/almir/static/js/main.js @@ -88,7 +88,7 @@ $(function () { }, "oTableTools": { sSwfPath: tabletools_swf, - aButtons: ["copy", "xls", "pdf", "print"] + aButtons: ["copy", "csv", "pdf", "print"] }, "iDisplayLength": 50, "bDestroy": true, @@ -99,11 +99,11 @@ $(function () { href; // clickable-row href = $this.find('td:first a').attr('href'); - if (typeof href == 'string') { + /*if (typeof href == 'string') { $this.addClass('clickable-row').click(function (e) { window.location = href; }); - } + } commented out.. is not usable when there are dropboxes in the rows, we can adjust it?*/ }); } }; diff --git a/almir/templates/base.jinja2 b/almir/templates/base.jinja2 index 2d12b46..14ae5de 100644 --- a/almir/templates/base.jinja2 +++ b/almir/templates/base.jinja2 @@ -48,9 +48,11 @@ {% endblock %} +