Commit 8cebacc6 authored by Nicolas Wavrant's avatar Nicolas Wavrant

Merge branch 'slaprunner-paas'

parents c4716eaa c9a34429
......@@ -200,9 +200,10 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
def _getRcode(self):
#XXX-Nicolas: hardcoded url. Best way right now to automate the tests...
monitoring_password = "passwordtochange"
monitor_url = self.monitor_url + "?script=zero-knowledge%2Fsettings.cgi"
result = self._opener_director.open(monitor_url,
"password=passwordtochange")
"password=" + monitoring_password + ";password_2=" + monitoring_password)
if result.getcode() is not 200:
raise NotHttpOkException(result.getcode())
......
......@@ -4,6 +4,7 @@
import ConfigParser
import datetime
import flask
import logging
import logging.handlers
import os
......@@ -133,4 +134,23 @@ def serve(config):
config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app)
def getUpdatedParameter(self, var):
configuration_parser = ConfigParser.SafeConfigParser()
configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
configuration_parser.read(configuration_file_path)
for section in configuration_parser.sections():
if configuration_parser.has_option(section, var):
return configuration_parser.get(section, var)
# if the requested var is dependant of flask
if var in self.keys():
temp_dict = dict()
temp_dict.update(self)
return temp_dict[var]
else:
raise KeyError
flask.config.Config.__getitem__ = getUpdatedParameter
run()
......@@ -96,8 +96,6 @@ class SlaprunnerTestCase(unittest.TestCase):
software_profile='software.cfg',
SECRET_KEY="123456",
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
auto_deploy = True,
autorun = False,
instance_monitoring_url = 'https://[' + config.ipv6_address + ']:9684',
)
self.app = views.app.test_client()
......@@ -122,6 +120,10 @@ class SlaprunnerTestCase(unittest.TestCase):
project = os.path.join(self.app.config['etc_dir'], '.project')
users = os.path.join(self.app.config['etc_dir'], '.users')
#reset tested parameters
self.updateConfigParameter('autorun', False)
self.updateConfigParameter('auto_deploy', True)
if os.path.exists(users):
os.unlink(users)
if os.path.exists(project):
......@@ -139,6 +141,15 @@ class SlaprunnerTestCase(unittest.TestCase):
killRunningProcess('slapgrid-cp', recursive=True)
killRunningProcess('slapgrid-sr', recursive=True)
def updateConfigParameter(self, parameter, value):
config_parser = ConfigParser.SafeConfigParser()
config_parser.read(os.getenv('RUNNER_CONFIG'))
for section in config_parser.sections():
if config_parser.has_option(section, parameter):
config_parser.set(section, parameter, str(value))
with open(os.getenv('RUNNER_CONFIG'), 'wb') as configfile:
config_parser.write(configfile)
def configAccount(self, username, password, email, name, rcode):
"""Helper for configAccount"""
return self.app.post('/configAccount',
......@@ -433,8 +444,8 @@ class SlaprunnerTestCase(unittest.TestCase):
"""Scenario 7: isSRReady won't overwrite the existing
Sofware Instance if it has been deployed yet"""
# Test that SR won't be deployed with auto_deploy=False
self.app.config['auto_deploy'] = False
self.app.config['autorun'] = False
self.updateConfigParameter('auto_deploy', False)
self.updateConfigParameter('autorun', False)
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test')
......@@ -442,7 +453,7 @@ class SlaprunnerTestCase(unittest.TestCase):
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "0")
# Test if auto_deploy parameter starts the deployment of SR
self.app.config['auto_deploy'] = True
self.updateConfigParameter('auto_deploy', True)
self.setupSoftwareFolder()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "2")
......@@ -522,6 +533,23 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertEqual(response, (1, MAX_RUN_INSTANCE))
def test_dynamicParametersReading(self):
"""Test if the value of a parameter can change in the flask application
only by changing the value of slapos.cfg config file. This can happen when
slapgrid processes the webrunner's partition.
"""
config_file = os.path.join(self.app.config['etc_dir'], 'slapos.cfg')
runner_config_old = os.environ['RUNNER_CONFIG']
os.environ['RUNNER_CONFIG'] = config_file
open(config_file, 'w').write("[section]\nvar=value")
config = self.app.config
self.assertEqual(config['var'], "value")
open(config_file, 'w').write("[section]\nvar=value_changed")
self.assertEqual(config['var'], "value_changed")
# cleanup
os.environ['RUNNER_CONFIG'] = runner_config_old
def main():
# Empty parser for now - so that erp5testnode is happy when doing --help
parser = argparse.ArgumentParser()
......
......@@ -28,6 +28,7 @@
.swith_btn{background: url(../images/gnome-session-switch.png) center right no-repeat;width: 105px;}
.flist_btn{background: url(../images/list2_down.png) center right no-repeat;width: 26px;}
.fmenu_btn{background: url(../images/ui_menu_blue.png) center right no-repeat;width: 58px;}
.shell_btn{background: url(../images/terminal.png) center right no-repeat;width: 62px;}
#tabControl{overflow: hidden;}
#tabControl .item{float:left; min-width: 60px; background: #D5D5D5; height: 22px; padding-top: 8px; font-size:1em;
......@@ -43,4 +44,42 @@ border-left:1px #E4E4E4 solid; cursor:pointer; color: #5C7077; text-shadow: 0px
#tabContent pre.active {display: block;}
.item-hide{display:none;}
#shell-window {
width: 974px;
height: 350px;
z-index: 10;
border: 2px solid #6A93A0;
border-top: none;
background: #9bbed6;
position: absolute;
display: none;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
#shell-result {
width: 98%;
height: 300px;
margin-left: 1%;
margin-right: 1%;
margin-top: 5px;
padding: 3px;
background: #E4E4E4;
color: #074A86;
box-sizing: border-box;
border: 2px inset #6A93A0;
overflow: auto;
}
.runned-command
{
font-weight: bold;
}
#shell-input {
width: 96%;
margin-left: 1%;
margin-right: 1%;
position: absolute;
bottom: 10px;
background: #E4E4E4;
color: #074A86;
border: inset 1px #678dad;
}
......@@ -106,7 +106,7 @@ body {
padding-top: 3px;
margin-left:20px;
float: left;
width: 700px;
width: 695px;
height: 22px;
text-align: center;
color: #4c6172;
......@@ -434,6 +434,10 @@ padding: 10px;height: 80px;padding-bottom:15px;}
.gradient{background-color:#f4f4f4;background:-moz-linear-gradient(#f4f4f4,#ececec);background:-webkit-linear-gradient(#f4f4f4,#ececec);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr='#f4f4f4',endColorstr='#ececec')";background:linear-gradient(#f4f4f4,#ececec);}
#slapstate {
display: none;
}
.log_content{
border: solid 1px #519ADA;
padding: 2px;
......@@ -506,6 +510,9 @@ padding: 10px;height: 80px;padding-bottom:15px;}
font-size: 16px;
}
.last_build{
font-size: 0.9em;
}
.log_content_box{
padding: 10px 5px;
font-size: 14px;
......@@ -528,13 +535,13 @@ padding: 10px;height: 80px;padding-bottom:15px;}
display: block;
float: left;
}
.log_info_box div.state_running span{
.log_info_box div.state_running span, .log_info_box div.state_notupdated span{
background: #FFC800;
}
.log_info_box div.state_terminated span{
background: #00C800;
}
.log_info_box div.state_waiting span, .log_info_box div.state_stopped span{
.log_info_box div.state_waiting span, .log_info_box div.state_stopped span, .log_info_box div.state_failed span{
background: #FF3131;
}
......
......@@ -59,7 +59,7 @@ $(document).ready(function () {
},
success: function (data) {
if (data.code === 1) {
url = 'https://' + $("input#username").val() + ':' + $("input#password").val() + '@' + location.host + $SCRIPT_ROOT + '/';
var url = 'https://' + $("input#username").val() + ':' + $("input#password").val() + '@' + location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
......
......@@ -89,9 +89,12 @@ $(document).ready(function () {
}
xml += '<instance>\n';
if (size > 1) {
for (i = 2; i <= size; i += 1) {
if ($('input#txt_' + i).val() !== '') {
xml += '<parameter id="' + $('input#txt_' + i).val() + '">' + $('textarea#value_' + i).val() + '</parameter>\n';
// we have to remove the 1st line, which just diplay column names
for (i = 0; i < size - 1; i += 1) {
var parameter_name = $("#partitionParameter tr").find("input")[i].value;
var parameter_value = $("#partitionParameter tr").find("textarea")[i].value;
if (parameter_name !== '') {
xml += '<parameter id="' + parameter_name + '">' + parameter_value + '</parameter>\n';
}
}
}
......
This diff is collapsed.
/*jslint undef: true */
/*global $, document, $SCRIPT_ROOT, window */
/*global path: true */
/* vim: set et sts=4: */
var shellHistory = "";
var currentCommand = 0;
$(document).ready(function () {
"use strict";
var updateHistory = function () {
$.getJSON("/getMiniShellHistory", function (data) {
shellHistory = data;
currentCommand = shellHistory.length;
});
};
updateHistory();
$("#shell").click (function() {
// We have to do that because once slide effect is activated, div is considered as visible
$("#shell").css("background-color", "#E4E4E4");
if ( ! $("#shell-window").is(':visible') ) {
$("#shell").css("background-color", "#C7C7C7");
}
$("#shell-window").slideToggle("fast");
if ( $("#shell-window").is(':visible') ) {
$("#shell-input").focus();
}
});
$("#shell-input").keypress(function (event) {
//if Enter is pressed
if(event.which === 13) {
event.preventDefault();
var command = $("#shell-input").val();
var data = { command: command };
$("#shell-result").append("<p id=\"waiting_for_command\"><img src=\"/static/css/images/loading-min.gif\" /></p>")
$("#shell-result").scrollTop($("#shell-result")[0].scrollHeight);
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + "/runCommand",
data: data,
timeout: 600000
})
.done( function (data) {
var data = "<p><span class=\"runned-command\">" + data.path + " >>> " + command + "</span></p><br/><pre>" + data.data + "</pre><br/>";
$("#shell-result").append(data);
$("#shell-input").val("");
$("#shell-result").scrollTop($("#shell-result")[0].scrollHeight);
updateHistory();
})
.fail( function(xhr, status, error) {
$("#error").Popup("Error while sending command. Server answered with :\n" + xhr.statusCode().status + " : " + error, {type: 'error', duration: 3000})
})
.always( function() {
$("#waiting_for_command").remove();
});
}
});
$("#shell-input").keydown(function (event) {
//if Key Up is pressed
if(event.which == 38) {
event.preventDefault();
currentCommand--;
if (currentCommand <= 0)
currentCommand = 0;
$("#shell-input").val(shellHistory[currentCommand]);
}
//if Key Down is pressed
if(event.which === 40) {
event.preventDefault();
currentCommand++;
if (currentCommand > shellHistory.length) {
currentCommand = shellHistory.length;
$("#shell-input").val("");
} else {
$("#shell-input").val(shellHistory[currentCommand]);
}
}
//if Tab is pressed
if(event.which === 9) {
event.preventDefault();
$("#error").Popup("Sorry, Tab completion is not handled for the moment in MiniShell", {type: 'info', duration: 1000})
}
});
});
/*jslint undef: true */
/*global $, document, window, processState, getCookie, setCookie, setSpeed, $SCRIPT_ROOT */
/*global openedlogpage: true */
/*global $, document, window, getCookie, setCookie, setSpeed, $SCRIPT_ROOT */
/*global openedlogpage: true, running: false */
/* vim: set et sts=4: */
$(document).ready(function () {
......@@ -53,7 +53,7 @@ $(document).ready(function () {
}
function updatelogBox() {
if (processState === "Stopped" || processState === "Checking" || $("#manual").is(":checked")) {
if (! running || $("#manual").is(":checked")) {
$("#salpgridLog").hide();
$("#manualLog").show();
$("#slapstate").hide();
......@@ -113,7 +113,7 @@ $(document).ready(function () {
})
.always(function() {
sending = false;
if (processState === "Stopped" || processState === "Checking" || $("#manual").is(":checked")) {
if (! running || $("#manual").is(":checked")) {
$("#logheader").html(info);
} else {
$("#logheader").html("Inspecting slapgrid log - Click for more options");
......
......@@ -44,9 +44,6 @@
$("#error").Popup($("input#fmsg").val(), {type:'info', duration:5000, load:true});
}
bindRun();
if(!checkSavedCmd()){
getRunningState();
}
$('ul.sf-menu').superfish({
delay: 600,
speed: 'fast',
......@@ -67,6 +64,10 @@
<div class="block_header">
<a href="{{ url_for('home') }}" style="float:left;" id="home" title="Home"><img alt="" src="{{ url_for('static', filename='images/home.png') }}" /></a>
<div class="line"></div>
<a href="http://community.slapos.org/wiki/osoe-Lecture.SlapOS.Extended" style="float:left;position:relative;top:1px;" target="_blank"><img src="{{ url_for('static', filename="images/doc.png")}}" alt="documentation" title="Documentation" /></a>
<div class="line"></div>
<a href="http://community.slapos.org/forum" style="float:left;position:relative;top:1px;" target="_blank"><img src="{{ url_for('static', filename="images/forum.png")}}" alt="forum" title="Forum" /></a>
<div class="line"></div>
<h2 class="info">{% block title %}{% endblock %} - {{session.title}}</h2>
<div class="run">
<div id="running" style="display:none">
......@@ -121,7 +122,7 @@
</div>
{% if request.path != '/login' %}
<div id="footer">
SlapOS web runner &copy; Vifib SARL 2011, 2012, 2013 - All right reserved - Creative Commons Shared Alike
SlapOS web runner &copy; Vifib SARL 2011-2014 - All right reserved - Creative Commons Shared Alike
</div>
{%endif%}
</div>
......
......@@ -10,6 +10,7 @@
<link href="{{ url_for('static', filename='css/colorbox.css', _external=False) }}" rel="stylesheet" type="text/css" media="screen" />
<script src="{{ url_for('static', filename='js/jquery/jquery.colorbox-min.js') }}" type="application/javascript" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/scripts/softwareFolder.js') }}" type="application/javascript" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/scripts/shell.js') }}" type="application/javascript" charset="utf-8"></script>
{% endblock %}
{% block body %}
<style>
......@@ -50,11 +51,18 @@
<li id="fullscreen"><span class="expand_editor" title="Show Editor in Full window. Hint: Use Ctrl+E">&nbsp;</span></li>
<li id="save"><span class="save_btn" title="Save current file. Hint: Use Ctrl+S">&nbsp;</span></li>
<li id="option"><span class="fmenu_btn" title='Show more options' rel='tooltip'>Menu</span></li>
<li id="shell"><span class="shell_btn" title="Run a command in a shell">Shell</span></li>
</ul>
<div id="tabControl"></div>
<div class="clear"></div>
</div>
<div class="clear"></div>
<div id="shell-window">
<div id="shell-result">
</div>
<input type="text" name="command" id="shell-input" autocomplete="off" placeholder="Type command ..." />
</div>
<div class="clear"></div>
<div class="software_details">
<div id="details_box">
<div id="fileTree" class="file_tree_short"></div>
......
......@@ -37,6 +37,8 @@
<p>Processing</p>
<div class="clear"></div>
</div>
<p id="last_build_software" class="last_build"></p><br/>
<div class="clear"></div>
<p>SlapOS rebuild your software from source, allowing you to easily patch or add any free software. <a href="{{ url_for('viewLog', logfile='software.log') }}">Learn how!</a></p>
</div>
......@@ -49,6 +51,8 @@
<p>Waiting for starting</p>
<div class="clear"></div>
</div>
<p id="last_build_instance" class="last_build"></p><br/>
<div class="clear"></div>
<p>SlapOS configure your running environment to match your needs. <a href="{{ url_for('viewLog', logfile='instance.log') }}">Learn how!</a></p>
</div>
......
......@@ -3,6 +3,7 @@
# pylint: disable-msg=W0311,C0301,C0103,C0111,W0141,W0142
import ConfigParser
import datetime
import json
import logging
import md5
......@@ -275,6 +276,29 @@ def isSoftwareRunning(config=None):
return isRunning('slapgrid-sr')
def slapgridResultToFile(config, step, returncode, datetime):
filename = step + "_info.json"
file = os.path.join(config['runner_workdir'], filename)
result = {'last_build':datetime, 'success':returncode}
open(file, "w").write(json.dumps(result))
def getSlapgridResult(config, step):
filename = step + "_info.json"
file = os.path.join(config['runner_workdir'], filename)
if os.path.exists(file):
result = json.loads(open(file, "r").read())
else:
result = {'last_build': 0, 'success':-1}
return result
def waitProcess(config, process, step):
process.wait()
date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
slapgridResultToFile(config, step, process.returncode, date)
def runSoftwareWithLock(config, lock=True):
"""
Use Slapgrid to compile current Software Release and wait until
......@@ -301,10 +325,13 @@ def runSoftwareWithLock(config, lock=True):
name='slapgrid-sr', stdout=None)
if lock:
slapgrid.wait()
date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
slapgridResultToFile(config, "software", slapgrid.returncode, date)
#Saves the current compile software for re-use
config_SR_folder(config)
return ( True if slapgrid.returncode == 0 else False )
else:
thread.start_new_thread(waitProcess, (config, slapgrid, "software"))
return False
......@@ -401,8 +428,11 @@ def runInstanceWithLock(config, lock=True):
name='slapgrid-cp', stdout=None)
if lock:
slapgrid.wait()
date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
slapgridResultToFile(config, "instance", slapgrid.returncode, date)
return ( True if slapgrid.returncode == 0 else False )
else:
thread.start_new_thread(waitProcess, (config, slapgrid, "instance"))
return False
......@@ -896,3 +926,17 @@ def setupDefaultSR(config):
configNewSR(config, config['default_sr'])
if config['auto_deploy']:
thread.start_new_thread(buildAndRun, (config,))
def setMiniShellHistory(config, command):
history_max_size = 10
command = command + "\n"
history_file = config['minishell_history_file']
if os.path.exists(history_file):
history = open(history_file, 'r').readlines()
if len(history) >= history_max_size:
del history[0]
else:
history = []
history.append(command)
open(history_file, 'w+').write(''.join(history))
......@@ -6,6 +6,7 @@
import json
import os
import shutil
import subprocess
import thread
import urllib
......@@ -14,7 +15,8 @@ from flask import (Flask, request, redirect, url_for, render_template,
from slapos.runner.process import killRunningProcess
from slapos.runner.utils import (checkSoftwareFolder, configNewSR,
createNewUser, getProfilePath,
createNewUser, getBuildAndRunParams,
getProfilePath, getSlapgridResult,
listFolder, getBuildAndRunParams,
getProjectTitle, getRcode, getSession,
getSlapStatus, getSvcStatus,
......@@ -26,6 +28,7 @@ from slapos.runner.utils import (checkSoftwareFolder, configNewSR,
removeSoftwareByName, runInstanceWithLock,
runSoftwareWithLock, runSlapgridUntilSuccess,
saveSession, saveBuildAndRunParams,
setMiniShellHistory,
svcStartStopProcess, svcStopAll, tail,
updateInstanceParameter)
......@@ -93,6 +96,10 @@ def myAccount():
params=getBuildAndRunParams(app.config))
def getSlapgridParameters():
return jsonify(getBuildAndRunParams(app.config))
def manageRepository():
public_key = open(app.config['public_key']).read()
account = getSession(app.config)
......@@ -215,6 +222,8 @@ def getFileLog():
else:
file_path = realpath(app.config, logfile)
try:
if not os.path.exists(file_path):
raise IOError
if not isText(file_path):
return jsonify(code=0,
result="Can not open binary file, please select a text file!")
......@@ -446,7 +455,15 @@ def slapgridResult():
if os.path.exists(app.config[log_file]):
log_result = readFileFrom(open(app.config[log_file]),
int(request.form['position']))
return jsonify(software=software_state, instance=instance_state,
build_result = getSlapgridResult(app.config, 'software')
run_result = getSlapgridResult(app.config, 'instance')
software_info = {'state':software_state,
'last_build':build_result['last_build'],
'success':build_result['success']}
instance_info = {'state':instance_state,
'last_build':run_result['last_build'],
'success':run_result['success']}
return jsonify(software=software_info, instance=instance_info,
result=(instance_state or software_state), content=log_result)
......@@ -691,6 +708,47 @@ def isSRReady():
return isSoftwareReleaseReady(app.config)
def runCommand():
cwd = open(app.config['minishell_cwd_file'], 'r').read().strip()
command = request.form.get("command", '').strip()
parsed_commands = command.split(';');
# does the user want to change current directory ?
for cmd in parsed_commands:
if 'cd' == cmd[:2]:
cmd = cmd.split(' ');
real_cmd = cmd[:]
if len(cmd) == 1:
cmd.append(os.environ.get('HOME'))
# shorten directory's name, to avoid things like : /a/../b
cd_dir = os.path.realpath(os.path.join(cwd, cmd[1]))
if os.path.exists(cd_dir) and os.path.isdir(cd_dir):
cwd = cd_dir
# save new cwd in the config file
open(app.config['minishell_cwd_file'], 'w').write(cwd)
# if the command was just cd, execute it. Otherwise, execute the rest
command = command.replace(' '.join(real_cmd), '').strip(';')
if not command:
return jsonify(path=cwd, data="Changed directory, now in : "+cwd)
try:
setMiniShellHistory(app.config, command)
command = "timeout 600 " + command
return jsonify(path=cwd, data=subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True, cwd=cwd))
except subprocess.CalledProcessError as e:
error = "Error : process exited with exit code " + str(e.returncode) + \
"\nProcess says :\n" + e.output
return error
def getMiniShellHistory():
history_file = app.config['minishell_history_file']
if not os.path.exists(history_file):
return ""
history = open(history_file, 'r').readlines()
for line, text in enumerate(history):
history[line] = text.strip()
return json.dumps(history)
#Setup List of URLs
app.add_url_rule('/', 'home', home)
app.add_url_rule('/browseWorkspace', 'browseWorkspace', browseWorkspace)
......@@ -773,3 +831,6 @@ app.add_url_rule("/editFile", 'editFile', editFile, methods=['GET'])
app.add_url_rule('/shell', 'shell', shell)
app.add_url_rule('/isSRReady', 'isSRReady', isSRReady)
app.add_url_rule('/addUser', 'addUser', addUser, methods=['POST'])
app.add_url_rule('/getSlapgridParameters', 'getSlapgridParameters', getSlapgridParameters, methods=['GET'])
app.add_url_rule('/runCommand', 'runCommand', runCommand, methods=['POST'])
app.add_url_rule("/getMiniShellHistory", 'getMiniShellHistory', getMiniShellHistory, methods=['GET'])
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment