Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions almir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions almir/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
job_states = (
('finished+running', 'Finished + Running'),
('scheduled', 'Scheduled'),
('disabled', 'Disabled'),
)


Expand Down
184 changes: 175 additions & 9 deletions almir/lib/bconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__))
Expand Down Expand Up @@ -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()
Expand All @@ -73,17 +92,17 @@ 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]
else:
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))
Expand Down Expand Up @@ -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?
Expand All @@ -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
Expand All @@ -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':
Expand Down Expand Up @@ -185,3 +350,4 @@ def send_command_by_polling(self, command, process=None):
output = nl2br(output)

return process, {"commands": [output]}

4 changes: 4 additions & 0 deletions almir/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']:
Expand Down
6 changes: 3 additions & 3 deletions almir/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ $(function () {
},
"oTableTools": {
sSwfPath: tabletools_swf,
aButtons: ["copy", "xls", "pdf", "print"]
aButtons: ["copy", "csv", "pdf", "print"]
},
"iDisplayLength": 50,
"bDestroy": true,
Expand All @@ -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?*/
});
}
};
Expand Down
13 changes: 13 additions & 0 deletions almir/templates/base.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@
{% endblock %}
</head>
<body>

<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">

<a class="brand" href="{{ request.route_url('about') }}">Almir</a>
{% for item in navigation_tree %}
<ul class="nav">
Expand All @@ -68,6 +70,17 @@
</div>
</div>



{% if request.params.get('okmsg') %}
<div class="okmessage">Ok: {{ request.params.get('okmsg') }}</div>
{% endif %}

{% if request.params.get('errormsg') %}
<div class="errormessage">Error: {{ request.params.get('errormsg') }}</div>
{% endif %}


<div class="container-fluid">
<div class="row-fluid">
{% block container %}
Expand Down
5 changes: 5 additions & 0 deletions almir/templates/job_list.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

{% block content %}
<h1 class="view">Job <small>list</small></h1>

{% if appstruct.state == 'scheduled' %}
<input type="submit" value="disable" />
{{ macros.upcoming_jobs_table(request, objects) }}
{% elif appstruct.state == 'disabled' %}
<input type="submit" value="enable" />
{{ macros.disabled_jobs_table(request, objects) }}
{% else %}
<table id="list_jobs" class="table table-bordered datatables">
</table>
Expand Down
26 changes: 26 additions & 0 deletions almir/templates/macros.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<th>Priority</th>
<th>Scheduled</th>
<th>Volume</th>
<th>Select</th>
</tr>
</thead>
<tbody>
Expand All @@ -37,12 +38,37 @@
<td>{{ job.priority }}</td>
<td>{{ job.date }} {{ job.time }}</td>
<td>{{ job.volume }}</td>
<td><input type="checkbox" /></td>
</tr>
{% endfor %}
</tbody>
</table>

{%- endmacro %}


{% macro disabled_jobs_table(request, jobs) -%}
<table class="table table-bordered datatables">
<thead>
<tr>
<th>Name</th>
<th>Select</th>
</tr>
</thead>
<tbody>
{% for job in jobs %}
<tr>
<td>{{ job.name }}</td>
<td><input type="checkbox" /></td>
</tr>
{% endfor %}
</tbody>
</table>

{%- endmacro %}



{% macro jobs_table(request, jobs) -%}
<table class="table table-bordered datatables">
<thead>
Expand Down
Loading