Commit 314206ec authored by Nicolas Wavrant's avatar Nicolas Wavrant

Merge branch 'slaprunner-paas'

parents 4eb93526 22c18547
from datetime import datetime
import argparse
import csv
import feedparser
import httplib # To avoid magic numbers
import io
import socket
import json
import time
import math
import httplib # To avoid magic numbers
import argparse
import os
import socket
import sys
import time
from datetime import datetime
from hashlib import sha512
from atomize import Entry
......@@ -19,6 +20,9 @@ from flask import abort
from flask import request
app = Flask(__name__)
# csv entries can be very large, increase limit.
csv.field_size_limit(sys.maxsize)
@app.route('/get/<feed>')
def get_feed(feed):
global app
......
......@@ -47,3 +47,17 @@ For reference: How-to deploy the whole test system
Note: the slapos nodes are currently deployed using slapos-in-partition.
Note: you have to manually kill -10 the erp5testnode process to start deployment of test because it doesn't know when SR installation is finished.
Note: you have to manually run slapos-node-software --all on the slapos nodes if you are developping the SR you are testing.
------------
STANDALONE TESTS
Here is an example on how to deploy standalone tests on the webrunner, which means without using erp5.
1/ Deploy a SlapRunner software instance using the type test.
2/ In slapos.org, you should tell on which server you want to deploy your instances. You can adapt to your case the parameter.xml above. For the first time, you can deploy all the instances on the same node, it will run the tests faster, and it will be easier to debug :
<?xml version='1.0' encoding='utf-8'?>
<instance>
<parameter id="_">{"cluster": {"-sla-0-computer_guid": "COMP-XXXX", "-sla-1-computer_guid": "COMP-XXXX", "-sla-2-computer_guid": "COMP-XXXX"}}</parameter>
</instance>
3/ Then go to the root instance folder : it is the one who has only "runStandaloneResiliencyTestSuite" in its bin folder.
4/ Run ./bin/runStandaloneResiliencyTestSuite and wait :) it would return "success" or "failure"
......@@ -32,6 +32,7 @@ import slapos.slap
import logging
import time
import os
class ResiliencyTestSuite(object):
"""
......@@ -104,6 +105,14 @@ class ResiliencyTestSuite(object):
"""
raise NotImplementedError('Overload me, I am an abstract method.')
def deleteTimestamp():
"""
XXX-Nicolas delete .timestamp in test partition to force the full processing
by slapgrid, to force the good parameters to be passed to the instances of the tree
"""
home = os.getenv('HOME')
timestamp = os.path.join(home, '.timestamp')
os.remove(timestamp)
def _getPartitionParameterDict(self):
"""
......@@ -115,6 +124,7 @@ class ResiliencyTestSuite(object):
software_type='resilient',
partition_reference=self.root_instance_name
).getConnectionParameterDict()
self.deleteTimestamp()
def _returnNewInstanceParameter(self, parameter_key, old_parameter_value):
"""
......@@ -126,8 +136,8 @@ class ResiliencyTestSuite(object):
new_parameter_value = None
while not new_parameter_value or new_parameter_value == 'None' or new_parameter_value == old_parameter_value:
self.logger.info('Not ready yet. SlapOS says new parameter value is %s' % new_parameter_value)
time.sleep(60)
new_parameter_value = self._getPartitionParameterDict().get(parameter_key, None)
time.sleep(120)
self.logger.info('New parameter value of instance is %s' % new_parameter_value)
return new_parameter_value
......
......@@ -28,6 +28,7 @@
from .resiliencytestsuite import ResiliencyTestSuite
import base64
import cookielib
import random
import string
......@@ -63,6 +64,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
300
)
def _connectToSlaprunner(self, resource, data=None):
"""
Utility.
......@@ -81,10 +83,8 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
def _login(self):
self.logger.debug('Logging in...')
self._connectToSlaprunner('doLogin', data='clogin=%s&cpwd=%s' % (
self.slaprunner_user,
self.slaprunner_password)
)
b64string = base64.encodestring('%s:%s' % (self.slaprunner_user, self.slaprunner_password))[:-1]
self._opener_director.addheaders = [('Authorization', 'Basic %s'%b64string)]
def _retrieveInstanceLogFile(self):
"""
......@@ -112,10 +112,17 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
return data
def _waitForSoftwareBuild(self):
while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1:
self.logger.info('Software release is still building. Sleeping...')
time.sleep(15)
self.logger.info('Software Release has been built / is no longer building.')
#while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1:
# self.logger.info('Software release is still building. Sleeping...')
# time.sleep(15)
#self.logger.info('Software Release has been built / is no longer building.')
try:
while self._connectToSlaprunner(resource='isSRReady') != "1":
self.logger.info('Software release is still building. Sleeping...')
time.sleep(15)
except (NotHttpOkException, urllib2.HTTPError):
# The nginx frontend might timeout before software release is finished.
self._waitForSoftwareBuild()
def _buildSoftwareRelease(self):
self.logger.info('Building the Software Release...')
......@@ -199,6 +206,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
self._openSoftwareRelease('helloworld')
self._buildSoftwareRelease()
time.sleep(15)
self._deployInstance()
self.data = self._retrieveInstanceLogFile()
......@@ -219,9 +227,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
)
self._login()
self._waitForSoftwareBuild()
# XXX: in theory, it should be done automatically by slaprunner.
# In practice, it is still too dangerous for ERP5 instances.
self._deployInstance()
time.sleep(15)
new_data = self._retrieveInstanceLogFile()
if new_data == self.data:
......
......@@ -6,51 +6,12 @@ import ConfigParser
import datetime
import logging
import logging.handlers
from optparse import OptionParser, Option
import os
import slapos.runner.process
from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys
from slapos.runner.utils import runInstanceWithLock
class Parser(OptionParser):
"""
Parse all arguments.
"""
def __init__(self, usage=None, version=None):
"""
Initialize all possible options.
"""
option_list = [
Option("-l", "--log_file",
help="The path to the log file used by the script.",
type=str),
Option("-v", "--verbose",
default=False,
action="store_true",
help="Verbose output."),
Option("-c", "--console",
default=False,
action="store_true",
help="Console output."),
Option("-d", "--debug",
default=False,
action="store_true",
help="Debug mode."),
]
OptionParser.__init__(self, usage=usage, version=version,
option_list=option_list)
def check_args(self):
"""
Check arguments
"""
(options, args) = self.parse_args()
if len(args) != 1:
self.error("Incorrect number of arguments")
return options, args[0]
from slapos.runner.views import *
class Config:
......@@ -61,19 +22,15 @@ class Config:
self.logger = None
self.verbose = None
def setConfig(self, option_dict, configuration_file_path):
def setConfig(self):
"""
Set options given by parameters.
"""
self.configuration_file_path = os.path.abspath(configuration_file_path)
# Set options parameters
for option, value in option_dict.__dict__.items():
setattr(self, option, value)
self.configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
# Load configuration file
configuration_parser = ConfigParser.SafeConfigParser()
configuration_parser.read(configuration_file_path)
# Merges the arguments and configuration
configuration_parser.read(self.configuration_file_path)
for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient", "cloud9_IDE"):
......@@ -88,17 +45,17 @@ class Config:
if self.console:
self.logger.addHandler(logging.StreamHandler())
if self.log_file:
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.log_file = self.log_dir + '/slaprunner.log'
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.logger.info("Started.")
self.logger.info(os.environ['PATH'])
......@@ -107,30 +64,33 @@ class Config:
self.logger.debug("Verbose mode enabled.")
def run():
"Run default configuration."
usage = "usage: %s [options] CONFIGURATION_FILE" % sys.argv[0]
try:
# Parse arguments
config = Config()
config.setConfig(*Parser(usage=usage).check_args())
def checkHtpasswd(config):
"""XXX:set for backward compatiblity
create a htpassword if etc/.users exist"""
user = os.path.join(config['etc_dir'], '.users')
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(user) and not os.path.exists(htpasswdfile):
data = open(user).read().strip().split(';')
htpasswd = HtpasswdFile(htpasswdfile, create=True)
htpasswd.update(data[0], data[1])
htpasswd.save()
else:
return
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
return_code = 0
except SystemExit as err:
# Catch exception raise by optparse
return_code = err
def run():
"Run default configuration."
# Parse arguments
config = Config()
config.setConfig()
sys.exit(return_code)
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
def serve(config):
from views import app
from werkzeug.contrib.fixers import ProxyFix
workdir = os.path.join(config.runner_workdir, 'project')
software_link = os.path.join(config.runner_workdir, 'softwareLink')
......@@ -145,14 +105,15 @@ def serve(config):
SECRET_KEY=os.urandom(24),
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
)
checkHtpasswd(app.config)
if not os.path.exists(workdir):
os.mkdir(workdir)
if not os.path.exists(software_link):
os.mkdir(software_link)
slapos.runner.process.setHandler()
setHandler()
config.logger.info('Running slapgrid...')
runInstanceWithLock(app.config)
config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app)
app.run(host=config.runner_host, port=int(config.runner_port),
debug=config.debug, threaded=True)
run()
......@@ -121,36 +121,40 @@ def getDiff(project):
result = safeResult(str(e))
return result
def gitCommit(project, msg):
"""Commit changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
repo = Repo(project)
if repo.is_dirty:
git = repo.git
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
else:
code = 1
json = "Nothing to be commited"
return jsonify(code=code, result=json)
def gitPush(project, msg):
"""Commit and Push changes for the specified repository
def gitPush(project):
"""Push changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
undo_commit = False
try:
repo = Repo(project)
if repo.is_dirty:
git = repo.git
current_branch = repo.active_branch.name
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
undo_commit = True
#push changes to repo
git.push('origin', current_branch)
code = 1
else:
json = "Nothing to commit"
code = 1
#push changes to repo
current_branch = repo.active_branch.name
git.push('origin', current_branch)
code = 1
except Exception as e:
if undo_commit:
git.reset("HEAD~") # undo previous commit
json = safeResult(str(e))
return jsonify(code=code, result=json)
......
......@@ -2,6 +2,14 @@
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904
#############################################
# !!! Attention !!!
# You now have to comment the last line
# in __init__py, wich starts the functiun
# run() in order to start the tests,
# or it will NOT work
#############################################
import argparse
import ConfigParser
import datetime
......@@ -13,10 +21,12 @@ import time
import unittest
from slapos.runner.utils import (getProfilePath, getSession, isInstanceRunning,
isSoftwareRunning, startProxy)
isSoftwareRunning, startProxy,
isSoftwareReleaseReady)
from slapos.runner.process import killRunningProcess, isRunning
from slapos.runner import views
import slapos.slap
from slapos.htpasswd import HtpasswdFile
#Helpers
......@@ -63,7 +73,6 @@ class SlaprunnerTestCase(unittest.TestCase):
self.project = 'slapos' # Default project name
self.template = 'template.cfg'
self.partitionPrefix = 'slappart'
self.slaposBuildout = "1.6.0-dev-SlapOS-010"
#create slaprunner configuration
config = Config()
config.setConfig()
......@@ -110,7 +119,6 @@ class SlaprunnerTestCase(unittest.TestCase):
shutil.rmtree(self.app.config['instance_root'])
if os.path.exists(self.app.config['software_link']):
shutil.rmtree(self.app.config['software_link'])
self.logout()
#Stop process
killRunningProcess('slapproxy', recursive=True)
killRunningProcess('slapgrid-cp', recursive=True)
......@@ -128,26 +136,11 @@ class SlaprunnerTestCase(unittest.TestCase):
),
follow_redirects=True)
def login(self, username, password):
"""Helper for Login method"""
return self.app.post('/doLogin',
data=dict(
clogin=username,
cpwd=password
),
follow_redirects=True)
def setAccount(self):
"""Initialize user account and log user in"""
response = loadJson(self.configAccount(self.users[0], self.users[1],
self.users[2], self.users[3], self.rcode))
response2 = loadJson(self.login(self.users[0], self.users[1]))
self.assertEqual(response['result'], "")
self.assertEqual(response2['result'], "")
def logout(self):
"""Helper for Logout current user"""
return self.app.get('/dologout', follow_redirects=True)
def updateAccount(self, newaccount, rcode):
"""Helper for update user account data"""
......@@ -186,8 +179,7 @@ class SlaprunnerTestCase(unittest.TestCase):
sr += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n"
sr += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n\n"
sr += "[versions]\nzc.buildout = %s\n" % self.slaposBuildout
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n zc.buildout\n\n"
os.mkdir(testSoftware)
open(os.path.join(testSoftware, self.app.config['software_profile']),
'w').write(sr)
......@@ -216,13 +208,6 @@ class SlaprunnerTestCase(unittest.TestCase):
"""Kill slapproxy process"""
killRunningProcess('slapproxy', recursive=True)
#Begin test case here
def test_wrong_login(self):
"""Test Login user before create session. This should return an error value"""
response = self.login(self.users[0], self.users[1])
#redirect to config account page
assert "<h2 class='title'>Your personal information</h2><br/>" in response.data
def test_configAccount(self):
"""For the first lauch of slaprunner user need do create first account"""
result = self.configAccount(self.users[0], self.users[1], self.users[2],
......@@ -232,34 +217,16 @@ class SlaprunnerTestCase(unittest.TestCase):
account = getSession(self.app.config)
self.assertEqual(account, self.users)
def test_login_logout(self):
"""test login with good and wrong values, test logout"""
response = loadJson(self.configAccount(self.users[0], self.users[1],
self.users[2], self.users[3], self.rcode))
self.assertEqual(response['result'], "")
result = loadJson(self.login(self.users[0], "wrongpwd"))
self.assertEqual(result['result'], "Login or password is incorrect, please check it!")
resultwr = loadJson(self.login("wronglogin", "wrongpwd"))
self.assertEqual(resultwr['result'], "Login or password is incorrect, please check it!")
#try now with true values
resultlg = loadJson(self.login(self.users[0], self.users[1]))
self.assertEqual(resultlg['result'], "")
#after login test logout
result = self.logout()
assert "<h2>Login to Slapos Web Runner</h2>" in result.data
def test_updateAccount(self):
"""test Update accound, this needs the user to log in"""
self.setAccount()
htpasswd = os.path.join(self.app.config['etc_dir'], '.htpasswd')
assert self.users[0] in open(htpasswd).read()
response = loadJson(self.updateAccount(self.updateUser, self.rcode))
self.assertEqual(response['code'], 1)
result = self.logout()
assert "<h2>Login to Slapos Web Runner</h2>" in result.data
#retry login with new values
response = loadJson(self.login(self.updateUser[0], self.updateUser[1]))
self.assertEqual(response['result'], "")
#log out now!
self.logout()
encode = HtpasswdFile(htpasswd, False)
encode.update(self.updateUser[0], self.updateUser[1])
assert self.updateUser[0] in open(htpasswd).read()
def test_startProxy(self):
"""Test slapproxy"""
......@@ -301,7 +268,6 @@ class SlaprunnerTestCase(unittest.TestCase):
),
follow_redirects=True))
self.assertEqual(response['result'], "")
self.logout()
def test_createSR(self):
"""Scenario 2: Create a new software release"""
......@@ -315,13 +281,10 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertEqual(response['result'], "")
currentSR = self.getCurrentSR()
assert newSoftware in currentSR
self.logout()
def test_openSR(self):
"""Scenario 3: Open software release"""
self.test_cloneProject()
#Login
self.login(self.users[0], self.users[1])
software = os.path.join(self.software, 'drupal') # Drupal SR must exist in SR folder
response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software),
......@@ -336,7 +299,6 @@ class SlaprunnerTestCase(unittest.TestCase):
# newSoftware = os.path.join(self.software, 'slaprunner-test')
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_runSoftware(self):
"""Scenario 4: CReate empty SR and save software.cfg file
......@@ -345,8 +307,6 @@ class SlaprunnerTestCase(unittest.TestCase):
#Call config account
#call create software Release
self.test_createSR()
#Login
self.login(self.users[0], self.users[1])
newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
......@@ -376,7 +336,6 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertTrue(os.path.exists(createdFile))
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_updateInstanceParameter(self):
"""Scenarion 5: Update parameters of current sofware profile"""
......@@ -419,13 +378,10 @@ class SlaprunnerTestCase(unittest.TestCase):
response = loadJson(self.app.get('/getParameterXml/dict'))
self.assertEqual(parameterDict, response['result']['instance'])
self.stopSlapproxy()
self.logout()
def test_requestInstance(self):
"""Scenarion 6: request software instance"""
self.test_updateInstanceParameter()
#Login
self.login(self.users[0], self.users[1])
self.proxyStatus(False, sleep_time=1)
#run Software profile
response = loadJson(self.app.post('/runSoftwareProfile',
......@@ -453,8 +409,34 @@ class SlaprunnerTestCase(unittest.TestCase):
assert 'simple file' in open(createdFile).read()
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_safeAutoDeploy(self):
"""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
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test')
project.close()
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.setupSoftwareFolder()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "2")
# Test that the new call to isSoftwareReleaseReady
# doesn't overwrite the previous installed one
completed_path = os.path.join(self.app.config['runner_workdir'],
'softwareLink', 'slaprunner-test', '.completed')
completed_text = ".completed file: test"
completed = open(completed_path, "w")
completed.write(completed_text)
completed.close()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "1")
assert completed_text in open(completed_path).read()
def main():
# Empty parser for now - so that erp5testnode is happy when doing --help
......
......@@ -297,6 +297,10 @@ input[type="radio"], input[type="checkbox"]{
margin-bottom:10px;
}
#commitmsg {
width:95%;
}
.message {
color:#FF5500;
line-height:21px;
......@@ -449,7 +453,7 @@ padding: 10px;height: 80px;padding-bottom:15px;}
}
#code{
float: right;
width: 692px;
width: 680px;
}
#details_head{margin-bottom: 10px;}
......@@ -728,3 +732,5 @@ padding:10px; font-size:14px; color:#03406A}
.login-input{width:220px;}
.information{display:block; float:left; height:16px; margin-top:10px; margin-left:10px; font-weight:bold}
.account{margin-left:60px;}
/* ********************/
#shellinabox{width:100%; min-height:530px;}
......@@ -43,6 +43,8 @@ $(document).ready(function () {
return false;
}
send = true;
var base_url = 'https://' + $("input#username").val() + ':'
+ $("input#password").val() + '@' + location.host
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + ((hasAccount) ? '/updateAccount' : '/configAccount'),
......@@ -55,7 +57,7 @@ $(document).ready(function () {
},
success: function (data) {
if (data.code === 1) {
window.location.href = $SCRIPT_ROOT + "/";
window.location.href = base_url + $SCRIPT_ROOT + '/';
} else {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
}
......
......@@ -90,3 +90,4 @@ function bindRemove() {
}
});
}(jQuery, document, this));
......@@ -27,7 +27,9 @@ $(document).ready(function () {
$("#login").removeClass("button").addClass("dsblebutton");
$.post(url, param, function (data) {
if (data.code === 1) {
window.location.href = $SCRIPT_ROOT + '/';
url = 'https://' + param.clogin + ':' + param.cpwd + '@'
+ location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else {
$("#error").Popup(data.result, {type: 'alert', duration: 3000});
}
......
......@@ -2,6 +2,13 @@
/*global $, document, $SCRIPT_ROOT */
/* vim: set et sts=4: */
$.valHooks.textarea = {
get: function (elem) {
"use strict";
return elem.value.replace(/\r?\n/g, "\r\n");
}
};
$(document).ready(function () {
"use strict";
......@@ -23,7 +30,7 @@ $(document).ready(function () {
urldata = $("input#workdir").val() + "/" + project;
$("#status").empty();
$("#push").hide();
$("#commit").hide();
$("#flash").empty();
if (project === "") {
$("#status").append("<h2>Please select one project...</h2><br/><br/>");
......@@ -45,7 +52,7 @@ $(document).ready(function () {
//alert(message);
$("#status").append("<p>" + message + "</p>");
if (data.dirty) {
$("#push").show();
$("#commit").show();
$("#status").append("<br/><h2>Display Diff for current Project</h2>");
$("#status").append("<p style='font-size:15px;'>You have changes in your project." +
" <a href='" + $SCRIPT_ROOT + "/getProjectDiff/"
......@@ -119,9 +126,9 @@ $(document).ready(function () {
checkout("0");
return false;
});
$("#commit").click(function () {
$("#commitbutton").click(function () {
if ($("input#commitmsg").val() === "" ||
$("input#commitmsg").val() === "Enter message...") {
$("textarea#commitmsg").val() === "Enter message...") {
$("#error").Popup("Please Enter the commit message", {type: 'alert', duration: 3000});
return false;
}
......@@ -131,12 +138,12 @@ $(document).ready(function () {
send = true;
var project = $("#project").val();
$("#imgwaitting").fadeIn('normal');
$("#commit").empty();
$("#commit").attr("value", "Wait...");
//$("#commit").empty();
$("#commitbbutton").attr("value", "Wait...");
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/pushProjectFiles',
data: {project: $("input#workdir").val() + "/" + project, msg: $("input#commitmsg").val()},
url: $SCRIPT_ROOT + '/commitProjectFiles',
data: {project: $("input#workdir").val() + "/" + project, msg: $("textarea#commitmsg").val()},
success: function (data) {
if (data.code === 1) {
if (data.result !== "") {
......@@ -144,19 +151,46 @@ $(document).ready(function () {
} else {
$("#error").Popup("Commit done!", {type: 'confirm', duration: 3000});
}
$("#commit").hide();
gitStatus();
} else {
$("#error").Popup(data.result, {type: 'error'});
}
$("#imgwaitting").hide();
$("#commit").empty();
$("#commit").attr("value", "Commit");
$("#commitmsg").empty();
$("#commitbutton").attr("value", "Commit");
send = false;
}
});
return false;
});
$("#push").click(function () {
if (send) {
return false;
}
send = true;
var project = $("#project").val();
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/pushProjectFiles',
data: {project: $("input#workdir").val() + "/" + project},
success: function (data) {
if (data.code === 1) {
if (data.result !== "") {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
} else {
$("#error").Popup("The local commits have correctly been saved on the server", {type: 'confirm', duration: 3000});
}
gitStatus();
} else {
$("#error").Popup(data.result, {type: 'error'});
}
$("#push").hide();
send = false;
}
});
return false;
});
/*
$("#pullbranch").click(function (){
if (send){
......
......@@ -55,9 +55,12 @@
<a href="{{ url_for('home') }}" style="float:left;" id="home" {% if request.path != '/' %}rel="tooltip"{% endif %} title="Home"><img alt="" src="{{ url_for('static', filename='images/home.png') }}" /></a>
<div class="line"></div>
<a href="{{ url_for('editCurrentProject') }}" style="float:left" title="Edit your current project"><img alt="" src="{{ url_for('static', filename='images/project.png') }}" /></a>
<div class="line"></div>
<a href="{{ url_for('manageProject') }}" style="float:left" title="Manage Your repositories"><img alt="" src="{{ url_for('static', filename='images/manage_repo-little.png') }}" /></a>
<div class="line"></div>
<a href="{{ url_for('shell') }}" style="float:left" title="Use the shell"><img alt="" src="{{ url_for('static', filename='images/terminal.png') }}" /></a>
<div class="line"></div>
<a href="{{ url_for('dologout') }}" style="float:left" title="Close your session"><img alt="" src="{{ url_for('static', filename='images/logout.png') }}" /></a>
<div class="line"></div>
<h2 class="info">{% block title %}{% endblock %} - {{session.title}}</h2>
<div class="run"><span id="running" style="display:none"><img alt="" src="{{ url_for('static', filename='images/ajax_roller.gif') }}"
height='26' title="slapgrid is currently running"/></span></div>
......
......@@ -40,15 +40,22 @@
<!--<img class="waitting" id="pullimgwaitting" src="{{ url_for('static', filename='images/waiting.gif') }}" alt="" />-->
</div>
</div>
<div id="push" style="margin-bottom:20px;">
<div id="commit" style="margin-bottom:20px;">
<h2>Commit All your changes (On active branch)</h2>
<div style="margin-left:15px;">
<label for='commitmsg'>Commit message: </label>
<input type="text" name="commitmsg" id="commitmsg" size='40' value="Enter message..." />
<input type="submit" name="commit" id ="commit" value="Commit" class="button"/>
<textarea name="commitmsg" id="commitmsg" cols="40" rows="3">
</textarea>
<input type="submit" name="commit" id ="commitbutton" value="Commit" class="button"/>
<img class="waitting" id="imgwaitting" src="{{ url_for('static', filename='images/waiting.gif') }}" alt="" />
</div>
</div>
<div id="push" style="margin-bottom:20px;">
<h2>Push your Last Commits</h2>
<div style="margin-left:15px;">
<input type="submit" name="push" id="push" value="Push" class="button"/>
</div>
</div>
<br/>
</div>
</div>
......
{% extends "layout.html" %}
{% block body %}
<iframe id="shellinabox" src="/shellinabox"></iframe>
{% endblock %}
......@@ -5,9 +5,10 @@
import logging
import md5
import multiprocessing
import os
import re
import shutil
import os
import thread
import time
import urllib
from xml.dom import minidom
......@@ -15,6 +16,8 @@ from xml.dom import minidom
import xml_marshaller
from flask import jsonify
from slapos.runner.gittools import cloneRepo
from slapos.runner.process import Popen, isRunning, killRunningProcess
from slapos.htpasswd import HtpasswdFile
import slapos.slap
......@@ -23,6 +26,7 @@ import slapos.slap
logger = logging.getLogger('werkzeug')
TRUE_VALUES = (1, '1', True, 'true', 'True')
html_escape_table = {
"&": "&amp;",
......@@ -276,12 +280,16 @@ def config_SR_folder(config):
if len(cfg) != 2:
continue # there is a broken config file
list.append(cfg[1])
folder_list = os.listdir(config['software_root'])
if os.path.exists(config['software_root']):
folder_list = os.listdir(config['software_root'])
else:
return
if not folder_list:
return
current_project = open(os.path.join(config['etc_dir'], ".project")).read()
projects = current_project.split('/')
name = projects[-2]
if current_project[-1] == '/':
current_project = current_project[:-1]
name = current_project.split('/')[-1]
for folder in folder_list:
if folder in list:
continue # this folder is already registered
......@@ -337,7 +345,7 @@ def runInstanceWithLock(config):
slapgrid = Popen([config['slapgrid_cp'], '-vc',
'--pidfile', slapgrid_pid,
config['configuration_file_path'], '--now'],
stdout=logfile, name='slapgrid-cp')
stdout=logfile, name='slapgrid-cp')
slapgrid.wait()
return True
......@@ -803,3 +811,58 @@ def readParameters(path):
return str(e)
else:
return "No such file or directory: %s" % path
def isSoftwareReleaseReady(config):
"""Return 1 if the Software Release has
correctly been deployed, 0 if not,
and 2 if it is currently deploying"""
project = os.path.join(config['etc_dir'], '.project')
if not os.path.exists(project):
return "0"
path = open(project, 'r').readline().strip()
software_name = path
if software_name[-1] == '/':
software_name = software_name[:-1]
software_name = software_name.split('/')[-1]
config_SR_folder(config)
if os.path.exists(os.path.join(config['runner_workdir'],
'softwareLink', software_name, '.completed')):
return "1"
else:
if isSoftwareRunning(config):
return "2"
elif config['auto_deploy'] in TRUE_VALUES:
configNewSR(config, path)
runSoftwareWithLock(config)
return "2"
else:
return "0"
def cloneDefaultGit(config):
"""Test if the default git has been downloaded yet
If not, download it in read-only mode"""
default_git = os.path.join(config['runner_workdir'],
'project', 'default_repo')
if not os.path.exists(default_git):
data = {'path': default_git,
'repo': config['default_repo'],
}
cloneRepo(data)
def buildAndRun(config):
runSoftwareWithLock(config)
runInstanceWithLock(config)
def setupDefaultSR(config):
"""If a default_sr is in the parameters,
and no SR is deployed yet, setup it
also run SR and Instance if required"""
project = os.path.join(config['etc_dir'], '.project')
if not os.path.exists(project) and config['default_sr'] != '':
configNewSR(config, config['default_sr'])
if config['auto_deploy']:
thread.start_new_thread(buildAndRun, (config,))
......@@ -7,7 +7,7 @@ import os
import shutil
import urllib
from flaskext.auth import Auth, AuthUser, login_required, logout
from flaskext.auth import logout
from flask import (Flask, request, redirect, url_for, render_template,
g, flash, jsonify, session, abort, send_file)
......@@ -17,7 +17,7 @@ from slapos.runner.utils import (checkSoftwareFolder, configNewSR, getFolder,
getProjectList, getProjectTitle, getSession,
getSlapStatus, getSvcStatus,
getSvcTailProcess, isInstanceRunning,
isSoftwareRunning, isText,
isSoftwareRunning, isSoftwareReleaseReady, isText,
loadSoftwareRList, md5sum, newSoftware,
readFileFrom, readParameters, realpath,
removeInstanceRoot, removeProxyDb,
......@@ -28,13 +28,11 @@ from slapos.runner.utils import (checkSoftwareFolder, configNewSR, getFolder,
from slapos.runner.fileBrowser import FileBrowser
from slapos.runner.gittools import (cloneRepo, gitStatus, switchBranch,
addBranch, getDiff, gitPush, gitPull)
addBranch, getDiff, gitCommit, gitPush, gitPull)
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024
auth = Auth(app, login_url_name='login')
auth.user_timeout = 0
file_request = FileBrowser(app.config)
# Setup default flask (werkzeug) parser
......@@ -46,18 +44,15 @@ def login_redirect(*args, **kwargs):
return redirect(url_for('login'))
#Access Control: Only static files and login pages are allowed to guest
@app.before_request
def before_request():
if request.path.startswith('/static'):
if request.path.startswith('/static') \
or request.path == '/isSRReady':
return
account = getSession(app.config)
if account:
user = AuthUser(username=account[0])
user.set_and_encrypt_password(account[1], "123400ZYX")
session['title'] = getProjectTitle(app.config)
g.users = {account[0]: user}
else:
session['title'] = "No account is defined"
if request.path != "/setAccount" and request.path != "/configAccount":
......@@ -65,13 +60,11 @@ def before_request():
# general views
@login_required()
def home():
return render_template('index.html')
# general views
@login_required()
def browseWorkspace():
return render_template('workspace.html')
......@@ -89,7 +82,6 @@ def setAccount():
return redirect(url_for('login'))
@login_required()
def myAccount():
account = getSession(app.config)
return render_template('account.html', username=account[0],
......@@ -102,7 +94,6 @@ def dologout():
return redirect(url_for('login'))
@login_required()
def configRepo():
public_key = open(app.config['public_key']).read()
account = getSession(app.config)
......@@ -113,16 +104,12 @@ def configRepo():
@app.route("/doLogin", methods=['POST'])
def doLogin():
username = request.form['clogin']
if username in g.users:
# Authenticate and log in!
if g.users[username].authenticate(request.form['cpwd']):
return jsonify(code=1, result="")
return jsonify(code=0, result="Login or password is incorrect, please check it!"), 401
#XXX Now has to check the .htpasswd if we want to warn
#the user that he misspelled his name/password
return jsonify(code=1, result="")
# software views
@login_required()
def editSoftwareProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['software_profile'])
if profile == "":
......@@ -131,14 +118,12 @@ def editSoftwareProfile():
profile=profile, projectList=getProjectList(app.config['workspace']))
@login_required()
def inspectSoftware():
return render_template('runResult.html', softwareRoot='software_link/',
softwares=loadSoftwareRList(app.config))
#remove content of compiled software release
@login_required()
def removeSoftware():
if isSoftwareRunning(app.config) or isInstanceRunning(app.config):
flash('Software installation or instantiation in progress, cannot remove')
......@@ -151,7 +136,6 @@ def removeSoftware():
return redirect(url_for('inspectSoftware'))
@login_required()
def runSoftwareProfile():
if runSoftwareWithLock(app.config):
return jsonify(result=True)
......@@ -159,7 +143,6 @@ def runSoftwareProfile():
return jsonify(result=False)
@login_required()
def viewSoftwareLog():
if os.path.exists(app.config['software_log']):
result = tail(open(app.config['software_log']), lines=1500)
......@@ -170,7 +153,6 @@ def viewSoftwareLog():
# instance views
@login_required()
def editInstanceProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['instance_profile'])
if profile == "":
......@@ -180,7 +162,6 @@ def editInstanceProfile():
# get status of all computer partitions and process state
@login_required()
def inspectInstance():
if os.path.exists(app.config['instance_root']):
file_path = 'instance_root'
......@@ -196,7 +177,6 @@ def inspectInstance():
#Reload instance process ans returns new value to ajax
@login_required()
def supervisordStatus():
result = getSvcStatus(app.config)
if not result:
......@@ -213,7 +193,6 @@ def supervisordStatus():
return jsonify(code=1, result=html)
@login_required()
def removeInstance():
if isInstanceRunning(app.config):
flash('Instantiation in progress, cannot remove')
......@@ -228,7 +207,6 @@ def removeInstance():
return redirect(url_for('inspectInstance'))
@login_required()
def runInstanceProfile():
if not os.path.exists(app.config['instance_root']):
os.mkdir(app.config['instance_root'])
......@@ -238,7 +216,6 @@ def runInstanceProfile():
return jsonify(result=False)
@login_required()
def viewInstanceLog():
if os.path.exists(app.config['instance_log']):
result = open(app.config['instance_log']).read()
......@@ -248,31 +225,26 @@ def viewInstanceLog():
result=result.encode("utf-8"))
@login_required()
def stopAllPartition():
svcStopAll(app.config)
return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def tailProcess(process):
return render_template('processTail.html',
process_log=getSvcTailProcess(app.config, process), process=process)
@login_required(login_redirect)
def startStopProccess(process, action):
svcStartStopProcess(app.config, process, action)
return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def openProject(method):
return render_template('projectFolder.html', method=method,
workDir='workspace')
@login_required()
def cloneRepository():
path = realpath(app.config, request.form['name'], False)
data = {
......@@ -284,27 +256,22 @@ def cloneRepository():
return cloneRepo(data)
@login_required()
def readFolder():
return getFolderContent(app.config, request.form['dir'])
@login_required()
def openFolder():
return getFolder(app.config, request.form['dir'])
@login_required()
def createSoftware():
return newSoftware(request.form['folder'], app.config, session)
@login_required()
def checkFolder():
return checkSoftwareFolder(request.form['path'], app.config)
@login_required()
def setCurrentProject():
if configNewSR(app.config, request.form['path']):
session['title'] = getProjectTitle(app.config)
......@@ -313,13 +280,11 @@ def setCurrentProject():
return jsonify(code=0, result=("Can not setup this Software Release"))
@login_required()
def manageProject():
return render_template('manageProject.html', workDir='workspace',
project=getProjectList(app.config['workspace']))
@login_required()
def getProjectStatus():
path = realpath(app.config, request.form['project'])
if path:
......@@ -329,7 +294,6 @@ def getProjectStatus():
#view for current software release files
@login_required()
def editCurrentProject():
project = os.path.join(app.config['etc_dir'], ".project")
if os.path.exists(project):
......@@ -340,7 +304,6 @@ def editCurrentProject():
#create file or directory
@login_required()
def createFile():
path = realpath(app.config, request.form['file'], False)
if not path:
......@@ -356,7 +319,6 @@ def createFile():
#remove file or directory
@login_required()
def removeFile():
try:
if request.form['type'] == "folder":
......@@ -368,7 +330,6 @@ def removeFile():
return jsonify(code=0, result=str(e))
@login_required()
def removeSoftwareDir():
try:
data = removeSoftwareByName(app.config, request.form['md5'],
......@@ -379,7 +340,6 @@ def removeSoftwareDir():
#read file and return content to ajax
@login_required()
def getFileContent():
file_path = realpath(app.config, request.form['file'])
if file_path:
......@@ -395,7 +355,6 @@ def getFileContent():
return jsonify(code=0, result="Error: No such file!")
@login_required()
def saveFileContent():
file_path = realpath(app.config, request.form['file'])
if file_path:
......@@ -405,7 +364,6 @@ def saveFileContent():
return jsonify(code=0, result="Error: No such file!")
@login_required()
def changeBranch():
path = realpath(app.config, request.form['project'])
if path:
......@@ -414,7 +372,6 @@ def changeBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def newBranch():
path = realpath(app.config, request.form['project'])
if path:
......@@ -426,23 +383,26 @@ def newBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required(login_redirect)
def getProjectDiff(project):
path = os.path.join(app.config['workspace'], project)
return render_template('projectDiff.html', project=project,
diff=getDiff(path))
def commitProjectFiles():
path = realpath(app.config, request.form['project'])
if path:
return gitCommit(path, request.form['msg'])
else:
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def pushProjectFiles():
path = realpath(app.config, request.form['project'])
if path:
return gitPush(path, request.form['msg'])
return gitPush(path)
else:
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def pullProjectFiles():
path = realpath(app.config, request.form['project'])
if path:
......@@ -451,7 +411,6 @@ def pullProjectFiles():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def checkFileType():
path = realpath(app.config, request.form['path'])
if not path:
......@@ -463,7 +422,6 @@ def checkFileType():
result="Can not open a binary file, please select a text file!")
@login_required()
def getmd5sum():
realfile = realpath(app.config, request.form['file'])
if not realfile:
......@@ -476,7 +434,6 @@ def getmd5sum():
#return information about state of slapgrid process
@login_required()
def slapgridResult():
software_state = isSoftwareRunning(app.config)
instance_state = isInstanceRunning(app.config)
......@@ -494,13 +451,11 @@ def slapgridResult():
result=(instance_state or software_state), content=log_result)
@login_required()
def stopSlapgrid():
result = killRunningProcess(request.form['type'])
return jsonify(result=result)
@login_required()
def getPath():
files = request.form['file'].split('#')
list = []
......@@ -519,7 +474,6 @@ def getPath():
return jsonify(code=1, result=realfile)
@login_required()
def saveParameterXml():
"""
Update instance parameter into a local xml file.
......@@ -550,7 +504,6 @@ def saveParameterXml():
return jsonify(code=1, result="")
@login_required()
def getSoftwareType():
software_type_path = os.path.join(app.config['etc_dir'], ".software_type.xml")
if os.path.exists(software_type_path):
......@@ -559,7 +512,6 @@ def getSoftwareType():
#read instance parameters into the local xml file and return a dict
@login_required()
def getParameterXml(request):
param_path = os.path.join(app.config['etc_dir'], ".parameter.xml")
if not os.path.exists(param_path):
......@@ -576,7 +528,6 @@ def getParameterXml(request):
#update user account data
@login_required()
def updateAccount():
code = request.form['rcode'].strip()
recovery_code = open(os.path.join(app.config['etc_dir'], ".rcode"), "r").read()
......@@ -622,7 +573,6 @@ def configAccount():
#Global File Manager
@login_required()
def fileBrowser():
if request.method == 'POST':
filename = request.form.get('filename', '').encode('utf-8')
......@@ -687,13 +637,19 @@ def fileBrowser():
return result
@login_required()
def editFile():
return render_template('editFile.html', workDir='workspace',
profile=urllib.unquote(request.args.get('profile', '')),
projectList=getProjectList(app.config['workspace']),
filename=urllib.unquote(request.args.get('filename', '')))
def shell():
return render_template('shell.html')
def isSRReady():
return isSoftwareReleaseReady(app.config)
#Setup List of URLs
app.add_url_rule('/', 'home', home)
......@@ -735,6 +691,8 @@ app.add_url_rule("/checkFileType", 'checkFileType', checkFileType,
methods=['POST'])
app.add_url_rule("/pullProjectFiles", 'pullProjectFiles', pullProjectFiles,
methods=['POST'])
app.add_url_rule("/commitProjectFiles", 'commitProjectFiles', commitProjectFiles,
methods=['POST'])
app.add_url_rule("/pushProjectFiles", 'pushProjectFiles', pushProjectFiles,
methods=['POST'])
app.add_url_rule("/getProjectDiff/<project>", 'getProjectDiff', getProjectDiff,
......@@ -774,3 +732,5 @@ app.add_url_rule("/updateAccount", 'updateAccount', updateAccount,
app.add_url_rule("/fileBrowser", 'fileBrowser', fileBrowser,
methods=['GET', 'POST'])
app.add_url_rule("/editFile", 'editFile', editFile, methods=['GET'])
app.add_url_rule('/shell', 'shell', shell)
app.add_url_rule('/isSRReady', 'isSRReady', isSRReady)
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