From f98a1aba841ad5834c2f85147ae362c448ec2e96 Mon Sep 17 00:00:00 2001 From: gypsymauro Date: Thu, 31 Jan 2013 14:56:18 +0100 Subject: [PATCH 01/14] Update almir/lib/bconsole.py --- almir/lib/bconsole.py | 89 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index a12f088..f406c09 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,14 @@ 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): + p = self.start_process() + log.debug('Sending command to bconsole: %s' % cmd) + stdout, stderr = p.communicate() + log.debug('Command output by bconsole:') + log.debug(stdout) + return stdout + def is_running(self): try: self.get_version() @@ -73,8 +85,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 +94,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,8 +123,8 @@ 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: @@ -121,8 +133,8 @@ def make_backup(self, job, level=None, storage=None, fileset=None, client=None, 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? @@ -149,6 +161,64 @@ def get_upcoming_jobs(self, days=1): return jobs + def mount_storage(self, storage, slot): + """Mounts the volume contained in the slot *slot* on the storage *storage*""" + + stdout = self.send_command('mount=%s slot=%d\n' % (storage,slot)) + + is_ok = stdout.find('is mounted') + + return is_ok != -1 + + + def unmount_storage(self, storage): + """Unmounts the storage *storage*""" + stdout = self.send_command('unmount=%s \n' % storage) + + is_ok = stdout.find('unmounted') + + 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) + + strcmd = 'delete %s \n' % cmd + + stdout = self.send_command(strcmd) + + 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 + + strcmd = 'label pool=%s storage=%s' % (pool,storage) + + if barcode: + strcmd += ( " barcode\n" ) + else: + strcmd += ( "\n%s\n" % label ) + + stdout = self.send_command(strcmd) + + is_ok = stdout.find('successfully created') + + return is_ok != -1 + + def send_command_by_polling(self, command, process=None): """""" if command == 'quit': @@ -185,3 +255,4 @@ def send_command_by_polling(self, command, process=None): output = nl2br(output) return process, {"commands": [output]} + From a03129e2299dd527c0e82018f57008a5d45011fd Mon Sep 17 00:00:00 2001 From: gypsymauro Date: Thu, 31 Jan 2013 14:57:03 +0100 Subject: [PATCH 02/14] Update almir/tests/test_bconsole.py --- almir/tests/test_bconsole.py | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/almir/tests/test_bconsole.py b/almir/tests/test_bconsole.py index 400b391..eb26ff9 100644 --- a/almir/tests/test_bconsole.py +++ b/almir/tests/test_bconsole.py @@ -21,6 +21,7 @@ def test_is_running(self): start_process.communicate.return_value = ('error', 'error') self.assertFalse(b.is_running()) + def test_from_temp_config(self): output = \ """# generated by almir, you should never edit this file. Do: @@ -101,3 +102,79 @@ def test_send_command_by_polling(self): process, outputs = b.send_command_by_polling('quit') self.assertEqual(outputs['commands'][0], 'Try harder.') + + + + + def test_mount_storage(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('is mounted', '') + self.assertTrue(b.mount_storage('File',0)) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.mount_storage('File',0)) + + + def test_unmount_storage(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('unmounted.', '') + self.assertTrue(b.unmount_storage('File')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.unmount_storage('File')) + + def test_delete(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('deleted', '') + self.assertTrue(b.delete(volume='test')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('deleted', '') + self.assertTrue(b.delete(jobid=1)) + + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.delete(volume='test')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.delete(jobid=1)) + + + def test_create_label(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('successfully created', '') + self.assertTrue(b.create_label(pool='test', label = 'testlabel')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('successfully created', '') + self.assertTrue(b.create_label(pool= 'test' , storage='test', label = 'testlabel')) + + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('successfully created', '') + self.assertTrue(b.create_label(pool='test', label = 'testlabel' )) + + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('successfully created', '') + self.assertTrue(b.create_label(pool='test', barcode = True)) From 2c88bd7193ed301fae5797665f655777207d65c1 Mon Sep 17 00:00:00 2001 From: gypsymauro Date: Thu, 31 Jan 2013 14:59:45 +0100 Subject: [PATCH 03/14] Update almir/static/datatables/TableTools.min.js --- almir/static/datatables/TableTools.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almir/static/datatables/TableTools.min.js b/almir/static/datatables/TableTools.min.js index 3455faf..e6505a3 100644 --- a/almir/static/datatables/TableTools.min.js +++ b/almir/static/datatables/TableTools.min.js @@ -67,7 +67,7 @@ if(""!==a.oScroll.sX)b.style.width=a.oApi._fnStringToCss(a.oScroll.sX),b.style.o if("none"!=g)b.push({node:d[j],display:g}),d[j].style.display="none"}"BODY"!=c.nodeName&&this._fnPrintHideNodes(c)}};TableTools._aInstances=[];TableTools._aListeners=[];TableTools.fnGetMasters=function(){for(var a=[],b=0,c=TableTools._aInstances.length;bPrint view

Please use your browser's print function to print this table. Press escape when finished.", sMessage:"",bShowAll:!0,sToolTip:"View print view",sButtonClass:"DTTT_button_print",sButtonClassHover:"DTTT_button_print_hover",sButtonText:"Print",fnMouseover:null,fnMouseout:null,fnClick:null,fnSelect:null,fnComplete:null,fnInit:null,fnCellRender:null},text:{sAction:"text",sToolTip:"",sButtonClass:"DTTT_button_text",sButtonClassHover:"DTTT_button_text_hover",sButtonText:"Text button",mColumns:"all",bHeader:!0,bFooter:!0,bSelectedOnly:!1,fnMouseover:null,fnMouseout:null,fnClick:null,fnSelect:null, From 315895f95df406e2c46e1ad62c90ab76eb3fa4cf Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 15:46:17 +0100 Subject: [PATCH 04/14] Revert "Update almir/static/datatables/TableTools.min.js" This reverts commit 2c88bd7193ed301fae5797665f655777207d65c1. --- almir/static/datatables/TableTools.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almir/static/datatables/TableTools.min.js b/almir/static/datatables/TableTools.min.js index e6505a3..3455faf 100644 --- a/almir/static/datatables/TableTools.min.js +++ b/almir/static/datatables/TableTools.min.js @@ -67,7 +67,7 @@ if(""!==a.oScroll.sX)b.style.width=a.oApi._fnStringToCss(a.oScroll.sX),b.style.o if("none"!=g)b.push({node:d[j],display:g}),d[j].style.display="none"}"BODY"!=c.nodeName&&this._fnPrintHideNodes(c)}};TableTools._aInstances=[];TableTools._aListeners=[];TableTools.fnGetMasters=function(){for(var a=[],b=0,c=TableTools._aInstances.length;bPrint view

Please use your browser's print function to print this table. Press escape when finished.", sMessage:"",bShowAll:!0,sToolTip:"View print view",sButtonClass:"DTTT_button_print",sButtonClassHover:"DTTT_button_print_hover",sButtonText:"Print",fnMouseover:null,fnMouseout:null,fnClick:null,fnSelect:null,fnComplete:null,fnInit:null,fnCellRender:null},text:{sAction:"text",sToolTip:"",sButtonClass:"DTTT_button_text",sButtonClassHover:"DTTT_button_text_hover",sButtonText:"Text button",mColumns:"all",bHeader:!0,bFooter:!0,bSelectedOnly:!1,fnMouseover:null,fnMouseout:null,fnClick:null,fnSelect:null, From e0395df9a19925825670a3457ab05fd6c586e313 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 16:04:44 +0100 Subject: [PATCH 05/14] Revert "Update almir/static/datatables/TableTools.min.js" This reverts commit 2c88bd7193ed301fae5797665f655777207d65c1. --- almir/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almir/static/js/main.js b/almir/static/js/main.js index 9c40d93..1da3fe3 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, From 7d09de644caebf4357cfd2cc51a1eb7e99778c37 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 16:39:52 +0100 Subject: [PATCH 06/14] commit test --- almir/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almir/static/js/main.js b/almir/static/js/main.js index 1da3fe3..5a68b49 100644 --- a/almir/static/js/main.js +++ b/almir/static/js/main.js @@ -7,7 +7,7 @@ maxlen: 200 */ /*global $, jQuery, tabletools_swf, deform, datatables_route */ -// Taken from: https://gist.github.com/1101534 +// Taken from: https://gist.github.com/1101534 jQuery.extend({ parseQuerystring: function () { var nvpair = {}, From e873d39554a99b81108c81738aef7791214381eb Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 16:50:38 +0100 Subject: [PATCH 07/14] Revert "commit test" This reverts commit 7d09de644caebf4357cfd2cc51a1eb7e99778c37. --- almir/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almir/static/js/main.js b/almir/static/js/main.js index 5a68b49..1da3fe3 100644 --- a/almir/static/js/main.js +++ b/almir/static/js/main.js @@ -7,7 +7,7 @@ maxlen: 200 */ /*global $, jQuery, tabletools_swf, deform, datatables_route */ -// Taken from: https://gist.github.com/1101534 +// Taken from: https://gist.github.com/1101534 jQuery.extend({ parseQuerystring: function () { var nvpair = {}, From b53cc40feefde3d038b3b1f40f833e5513748aae Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 18:38:44 +0100 Subject: [PATCH 08/14] added enable/disable/estimate and relative test suite --- almir/lib/bconsole.py | 57 +++++++++++++++++++++++++++++++----- almir/tests/test_bconsole.py | 40 ++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index f406c09..2444f11 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -164,7 +164,8 @@ def get_upcoming_jobs(self, days=1): def mount_storage(self, storage, slot): """Mounts the volume contained in the slot *slot* on the storage *storage*""" - stdout = self.send_command('mount=%s slot=%d\n' % (storage,slot)) + cmd = 'mount=%s slot=%d\n' % (storage,slot) + stdout = self.send_command(cmd) is_ok = stdout.find('is mounted') @@ -173,7 +174,8 @@ def mount_storage(self, storage, slot): def unmount_storage(self, storage): """Unmounts the storage *storage*""" - stdout = self.send_command('unmount=%s \n' % storage) + cmd = 'unmount=%s \n' % storage + stdout = self.send_command(cmd) is_ok = stdout.find('unmounted') @@ -191,9 +193,9 @@ def delete(self, volume=None, jobid=None): if jobid: cmd = ' jobid=%d ' % (jobid) - strcmd = 'delete %s \n' % cmd + cmd = 'delete %s \n' % cmd - stdout = self.send_command(strcmd) + stdout = self.send_command(cmd) is_ok = stdout.find('deleted') @@ -205,20 +207,59 @@ def create_label(self, pool, storage='', label = None, barcode = False ): if not label and not barcode: return False # we need or manual label or barcode - strcmd = 'label pool=%s storage=%s' % (pool,storage) + cmd = 'label pool=%s storage=%s' % (pool,storage) if barcode: - strcmd += ( " barcode\n" ) + cmd += ( " barcode\n" ) else: - strcmd += ( "\n%s\n" % label ) + cmd += ( "\n%s\n" % label ) - stdout = self.send_command(strcmd) + 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': diff --git a/almir/tests/test_bconsole.py b/almir/tests/test_bconsole.py index eb26ff9..62a4a57 100644 --- a/almir/tests/test_bconsole.py +++ b/almir/tests/test_bconsole.py @@ -118,7 +118,6 @@ def test_mount_storage(self): start_process.communicate.return_value = ('error', 'error') self.assertFalse(b.mount_storage('File',0)) - def test_unmount_storage(self): b = BConsole() with patch.object(b, 'start_process') as mock_method: @@ -154,7 +153,6 @@ def test_delete(self): start_process.communicate.return_value = ('error', 'error') self.assertFalse(b.delete(jobid=1)) - def test_create_label(self): b = BConsole() with patch.object(b, 'start_process') as mock_method: @@ -167,14 +165,48 @@ def test_create_label(self): start_process.communicate.return_value = ('successfully created', '') self.assertTrue(b.create_label(pool= 'test' , storage='test', label = 'testlabel')) - with patch.object(b, 'start_process') as mock_method: start_process = mock_method.return_value start_process.communicate.return_value = ('successfully created', '') self.assertTrue(b.create_label(pool='test', label = 'testlabel' )) - with patch.object(b, 'start_process') as mock_method: start_process = mock_method.return_value start_process.communicate.return_value = ('successfully created', '') self.assertTrue(b.create_label(pool='test', barcode = True)) + + def test_disable_job(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('disabled', '') + self.assertTrue(b.disable_job('testjob')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.disable_job('testjob')) + + def test_enable_job(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('enabled', '') + self.assertTrue(b.enable_job('testjob')) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertFalse(b.enable_job('testjob')) + + def test_estimate_job(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ("2000 OK estimate files=1000 bytes=10,000,000", '') + self.assertTrue(b.estimate_job('testjob')==(1000,10000000)) + + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ('error', 'error') + self.assertTrue(b.estimate_job('testjob')==(-1,-1)) From c6a985abc9521068b4375de8d1dd42a174804636 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 31 Jan 2013 19:17:23 +0100 Subject: [PATCH 09/14] missed to pass cmd to communicate and initial work on gui's tables --- almir/lib/bconsole.py | 4 ++-- almir/templates/storage_list.jinja2 | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index 2444f11..a70f4f6 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -70,9 +70,9 @@ def start_process(self): return Popen(shlex.split(self.bconsole_command), stdout=PIPE, stdin=PIPE, stderr=PIPE) def send_command(self, cmd): - p = self.start_process() log.debug('Sending command to bconsole: %s' % cmd) - stdout, stderr = p.communicate() + p = self.start_process() + stdout, stderr = p.communicate(cmd) log.debug('Command output by bconsole:') log.debug(stdout) return stdout diff --git a/almir/templates/storage_list.jinja2 b/almir/templates/storage_list.jinja2 index 95f8e04..f8b7ee3 100644 --- a/almir/templates/storage_list.jinja2 +++ b/almir/templates/storage_list.jinja2 @@ -9,14 +9,16 @@ Name Volumes Total backups + Select {% for storage in objects %} - + {{ macros.link(storage.Storage.render_name(request)) }} {{ storage.num_volumes }} {{ storage.Storage.format_byte_size(storage.total_backup_size) }} + {% endfor %} From 211824bce348abda37bf6e138afcb11bc9fe81a1 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Fri, 1 Feb 2013 15:38:56 +0100 Subject: [PATCH 10/14] managed status of jobs (enabled/disabled) and initial work on web gui --- almir/lib/bconsole.py | 30 +++++++++++++++++++++++++++++ almir/templates/job_list.jinja2 | 3 +++ almir/templates/macros.jinja2 | 5 +++++ almir/templates/storage_list.jinja2 | 2 +- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index a70f4f6..31e3624 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -144,7 +144,36 @@ def get_upcoming_jobs(self, days=1): except IndexError: return [] + # get the list of disabled jobs + stdout = self.send_command('show disabled\n') + +# example of header: +# 1000 OK: localhost-dir Version: 5.2.6 (21 February 2012) +# Enter a period to cancel a command. +# show disabled +# Disabled Jobs: +# BackupCatalog +# + + if stdout.find('No disabled Jobs.') == -1 : + disabled=[x.strip() for x in stdout.split('\n')[4:-1] if x!='Disabled Jobs:'] + else: + disabled=[] + jobs = [] + + for job in disabled: + jobs.append({ + 'level': '', + 'type': '', + 'priority': '', + 'date': '', + 'time': '', + 'name': job, + 'volume': '', + 'enabled': 'Y' , + }) + for line in unparsed_jobs.split('\n'): if not line.strip(): continue @@ -157,6 +186,7 @@ def get_upcoming_jobs(self, days=1): 'time': line[38:44].strip(), 'name': line[47:67].strip(), 'volume': line[67:].strip(), + 'enabled': 'N' , }) return jobs diff --git a/almir/templates/job_list.jinja2 b/almir/templates/job_list.jinja2 index af1547b..fa813bd 100644 --- a/almir/templates/job_list.jinja2 +++ b/almir/templates/job_list.jinja2 @@ -2,6 +2,9 @@ {% block content %}

Job list

+ + + {% if appstruct.state == 'scheduled' %} {{ macros.upcoming_jobs_table(request, objects) }} {% else %} diff --git a/almir/templates/macros.jinja2 b/almir/templates/macros.jinja2 index e2e0374..bb75b23 100644 --- a/almir/templates/macros.jinja2 +++ b/almir/templates/macros.jinja2 @@ -26,6 +26,8 @@ Priority Scheduled Volume + Enabled + Select @@ -37,10 +39,13 @@ {{ job.priority }} {{ job.date }} {{ job.time }} {{ job.volume }} + {{ job.enabled }} + {% endfor %} + {%- endmacro %} {% macro jobs_table(request, jobs) -%} diff --git a/almir/templates/storage_list.jinja2 b/almir/templates/storage_list.jinja2 index f8b7ee3..29fc237 100644 --- a/almir/templates/storage_list.jinja2 +++ b/almir/templates/storage_list.jinja2 @@ -3,7 +3,7 @@ {% block container %}

Storage list

By name

- +
From 5a1f0403105b4c95a3d109e087259a54ab6b4099 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Mon, 4 Feb 2013 11:20:27 +0100 Subject: [PATCH 11/14] split upcoming jobs method and fixed some test functions --- almir/lib/bconsole.py | 43 +++++++++++++++++------------------- almir/tests/test_bconsole.py | 17 ++++++++++++++ 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index 31e3624..8cae376 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -131,19 +131,7 @@ def make_backup(self, job, level=None, storage=None, fileset=None, client=None, # TODO: stderr why job failed? return False - def get_upcoming_jobs(self, days=1): - """""" - - stdout = self.send_command('.status dir scheduled days=%d\n' % days) - - #if stderr.strip(): - # pass # TODO: display flash? - - try: - unparsed_jobs = stdout.split('===================================================================================\n')[1].split('====\n')[0] - except IndexError: - return [] - + def get_disabled_jobs(self): # get the list of disabled jobs stdout = self.send_command('show disabled\n') @@ -163,17 +151,26 @@ def get_upcoming_jobs(self, days=1): jobs = [] for job in disabled: - jobs.append({ - 'level': '', - 'type': '', - 'priority': '', - 'date': '', - 'time': '', - 'name': job, - 'volume': '', - 'enabled': 'Y' , + jobs.append({'name': job, }) + return jobs + + def get_upcoming_jobs(self, days=1): + """""" + + stdout = self.send_command('.status dir scheduled days=%d\n' % days) + + #if stderr.strip(): + # pass # TODO: display flash? + + try: + unparsed_jobs = stdout.split('===================================================================================\n')[1].split('====\n')[0] + except IndexError: + return [] + + jobs = [] + for line in unparsed_jobs.split('\n'): if not line.strip(): continue @@ -186,11 +183,11 @@ def get_upcoming_jobs(self, days=1): 'time': line[38:44].strip(), 'name': line[47:67].strip(), 'volume': line[67:].strip(), - 'enabled': 'N' , }) return jobs + def mount_storage(self, storage, slot): """Mounts the volume contained in the slot *slot* on the storage *storage*""" diff --git a/almir/tests/test_bconsole.py b/almir/tests/test_bconsole.py index 62a4a57..62f5a2c 100644 --- a/almir/tests/test_bconsole.py +++ b/almir/tests/test_bconsole.py @@ -83,6 +83,23 @@ def test_get_upcoming_jobs(self): 'type': 'Backup', 'volume': '*unknown*'}]) + + def test_get_disabled_jobs(self): + b = BConsole() + with patch.object(b, 'start_process') as mock_method: + start_process = mock_method.return_value + start_process.communicate.return_value = ("""1000 OK: localhost-dir Version: 5.2.6 (21 February 2012) +Enter a period to cancel a command. +show disabled +Disabled Jobs: + Backupclient1 +""", '') + + jobs = b.get_disabled_jobs() + self.assertEqual(jobs, [{'name': 'Backupclient1'}]) + + + def test_send_command_by_polling(self): b = BConsole() with patch.object(b, 'start_process') as mock_method: From 79ecb09badd36e8f936c355b001baf675cd16873 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Wed, 6 Feb 2013 16:00:12 +0100 Subject: [PATCH 12/14] again some initial works on web gui --- almir/forms.py | 1 + almir/lib/bconsole.py | 21 ++++++++++++++------- almir/models.py | 4 ++++ almir/templates/job_list.jinja2 | 6 ++++-- almir/templates/macros.jinja2 | 25 +++++++++++++++++++++++-- almir/tests/test_bconsole.py | 5 +---- 6 files changed, 47 insertions(+), 15 deletions(-) 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 8cae376..6bfee38 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -75,6 +75,13 @@ def send_command(self, cmd): 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')] + + # removinf you have messages. msg + stdout = stdout.replace('You have messages.\n','') return stdout def is_running(self): @@ -136,17 +143,17 @@ def get_disabled_jobs(self): stdout = self.send_command('show disabled\n') # example of header: -# 1000 OK: localhost-dir Version: 5.2.6 (21 February 2012) -# Enter a period to cancel a command. -# show disabled # Disabled Jobs: # BackupCatalog # - if stdout.find('No disabled Jobs.') == -1 : - disabled=[x.strip() for x in stdout.split('\n')[4:-1] if x!='Disabled Jobs:'] - else: - disabled=[] + + 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 = [] 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/templates/job_list.jinja2 b/almir/templates/job_list.jinja2 index fa813bd..888b9fe 100644 --- a/almir/templates/job_list.jinja2 +++ b/almir/templates/job_list.jinja2 @@ -2,11 +2,13 @@ {% block content %}

Job list

- - {% if appstruct.state == 'scheduled' %} + {{ macros.upcoming_jobs_table(request, objects) }} + {% elif appstruct.state == 'disabled' %} + + {{ macros.disabled_jobs_table(request, objects) }} {% else %}
Name
diff --git a/almir/templates/macros.jinja2 b/almir/templates/macros.jinja2 index bb75b23..78f3694 100644 --- a/almir/templates/macros.jinja2 +++ b/almir/templates/macros.jinja2 @@ -26,7 +26,6 @@ Priority Scheduled Volume - Enabled Select @@ -39,7 +38,6 @@ {{ job.priority }} {{ job.date }} {{ job.time }} {{ job.volume }} - {{ job.enabled }} {% endfor %} @@ -48,6 +46,29 @@ {%- endmacro %} + +{% macro disabled_jobs_table(request, jobs) -%} + + + + + + + + + {% for job in jobs %} + + + + + {% endfor %} + +
NameSelect
{{ job.name }}
+ +{%- endmacro %} + + + {% macro jobs_table(request, jobs) -%} diff --git a/almir/tests/test_bconsole.py b/almir/tests/test_bconsole.py index 62f5a2c..261b270 100644 --- a/almir/tests/test_bconsole.py +++ b/almir/tests/test_bconsole.py @@ -88,10 +88,7 @@ def test_get_disabled_jobs(self): b = BConsole() with patch.object(b, 'start_process') as mock_method: start_process = mock_method.return_value - start_process.communicate.return_value = ("""1000 OK: localhost-dir Version: 5.2.6 (21 February 2012) -Enter a period to cancel a command. -show disabled -Disabled Jobs: + start_process.communicate.return_value = ("""Disabled Jobs: Backupclient1 """, '') From fbd91b8119df64df70831740bbfdbd1b1da07006 Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Thu, 7 Feb 2013 17:54:40 +0100 Subject: [PATCH 13/14] some works on web UI again --- almir/__init__.py | 5 ++++ almir/lib/bconsole.py | 2 +- almir/static/js/main.js | 4 +-- almir/templates/base.jinja2 | 13 ++++++++++ almir/templates/storage_list.jinja2 | 14 ++++++++-- almir/views.py | 40 +++++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 5 deletions(-) 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/lib/bconsole.py b/almir/lib/bconsole.py index 6bfee38..db12908 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -195,7 +195,7 @@ def get_upcoming_jobs(self, days=1): return jobs - def mount_storage(self, storage, slot): + 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) diff --git a/almir/static/js/main.js b/almir/static/js/main.js index 1da3fe3..6dabef7 100644 --- a/almir/static/js/main.js +++ b/almir/static/js/main.js @@ -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 %} +
+ + +
+ + +
+
@@ -18,9 +24,13 @@ - + {% endfor %}
Name{{ macros.link(storage.Storage.render_name(request)) }} {{ storage.num_volumes }} {{ storage.Storage.format_byte_size(storage.total_backup_size) }}
+ + + + {% endblock %} diff --git a/almir/views.py b/almir/views.py index 492323b..8a63be3 100644 --- a/almir/views.py +++ b/almir/views.py @@ -4,6 +4,9 @@ from deform import Form, ValidationFailure from pyramid.interfaces import IRoutesMapper from pyramid.settings import asbool +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPMovedPermanently, HTTPFound + from sqlalchemy import String, LargeBinary from sqlalchemy.sql.expression import desc, or_ from sqlalchemy.sql.functions import sum, count @@ -296,3 +299,40 @@ def datatables(request): 'iTotalRecords': total_records, 'aaData': aaData, } + + +def modify_storage(request): + bc = BConsole() + + enderer='templates/storage_list.jinja2', + + storages = ['File',] + + log.debug('###################################') + log.debug(request.params) + log.debug('###################################') + + command = request.POST.get('command') + if command == 'unmount': + ret = {'okmsg':'Storage unmounted correctly'} + for storage in storages: + if not bc.unmount_storage(storage): + msg = 'Error unmounting storage: %s' % storage + log.info(msg) + ret = {'errormsg':msg} + break + else: + ret = {'okmsg':'Storage mounted correctly'} + for storage in storages: + if not bc.mount_storage(storage): + msg = 'Error mounting storage: %s' % storage + log.info(msg) + ret = {'errormsg':msg} + break + + redirect_url = request.route_url('storage_list', _query=ret) + response = HTTPFound(location=redirect_url) + + return response + + From f9df9518df81c75c34264b04c8136dafc6406dfe Mon Sep 17 00:00:00 2001 From: coloriomauro Date: Mon, 11 Feb 2013 12:20:11 +0100 Subject: [PATCH 14/14] added release and update slots methods --- almir/lib/bconsole.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/almir/lib/bconsole.py b/almir/lib/bconsole.py index db12908..3724622 100644 --- a/almir/lib/bconsole.py +++ b/almir/lib/bconsole.py @@ -80,7 +80,7 @@ def send_command(self, cmd): # for i in range(3): # stdout = stdout[:stdout.find('\n')] - # removinf you have messages. msg + # removing you have messages. msg stdout = stdout.replace('You have messages.\n','') return stdout @@ -216,6 +216,26 @@ def unmount_storage(self, storage): 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"""