Commit becd5186 authored by Alain Takoudjou's avatar Alain Takoudjou

Merge branch 'slaprunner-paas' into slaprunner-layout

Conflicts:
	slapos/runner/static/css/styles.css
	slapos/runner/static/js/scripts/process.js
	slapos/runner/static/js/scripts/softwareFolder.js
	slapos/runner/static/js/scripts/viewlog.js
	slapos/runner/templates/account.html
	slapos/runner/templates/layout.html
	slapos/runner/templates/softwareFolder.html
	slapos/runner/templates/viewLog.html
	slapos/runner/views.py
parents e8176c6c 2de7ba00
0.39 (2014-02-20)
-----------------
* Slaprunner: new web interface design
* Slaprunner: one function handle both "run software" and "run instance" [9c660c0]
* Slaprunner: building and deploying can be customized [0db1f6b, b33bd1f]
* Slaprunner: adds a multi-user feature [efad6d]
* Slaprunner: add fullscreen mode for text edition [83d1dc]
* Slaprunner: direct access to monitoring of running instance, if it exists [f8e7bf3]
0.38.1 (2013-12-06)
-------------------
* Slaprunner: do not delete proxy.db on each run software [71777fc0]
0.38 (2013-12-03)
-----------------
* Slaprunner: adds an integrated shell [ca6a670a]
* Slaprunner: uses basic authentification [05913751]
* Slaprunner: adds automated deployment of a Software Release [c8ab1273]
* Slaprunner: flask development server replaced by Gunicorn, a WSGI server [48d60d0f]
* Slaprunner: new test scenario for auto-deployment [c6007954]
* Runner resiliencytestsuite: adds basic auth support [3c03f12b]
* Runner resiliencytestsuite: tests can be done on only one Slapos node [07198d87]
0.37.4 (2013-10-15)
-------------------
* Improve QEMU QMP wrapper by adding drive-backup method and other helpers. [0afb7d6, 95d0c8b]
0.37.3 (2013-10-10)
-------------------
* pubsub: don't swallow output of subprocess to allow debug. [c503484]
0.37.2 (2013-10-10)
-------------------
* Add QEMU QMP wrapper. [9e819a8]
* KVM resiliency test: update docstring about how to setup disk image. [dbe347f]
* KVM resiliency test: change key for each clone. [7ef1db3]
0.37.1 (2013-10-03) 0.37.1 (2013-10-03)
------------------- -------------------
......
...@@ -2,7 +2,7 @@ from setuptools import setup, find_packages ...@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
import glob import glob
import os import os
version = '0.37.1' version = '0.39'
name = 'slapos.toolbox' name = 'slapos.toolbox'
long_description = open("README.txt").read() + "\n" + \ long_description = open("README.txt").read() + "\n" + \
open("CHANGES.txt").read() + "\n" open("CHANGES.txt").read() + "\n"
...@@ -70,6 +70,7 @@ setup(name=name, ...@@ -70,6 +70,7 @@ setup(name=name,
'onetimeupload = slapos.onetimeupload:main', 'onetimeupload = slapos.onetimeupload:main',
'pubsubnotifier = slapos.pubsub.notifier:main', 'pubsubnotifier = slapos.pubsub.notifier:main',
'pubsubserver = slapos.pubsub:main', 'pubsubserver = slapos.pubsub:main',
'qemu-qmp-client = slapos.qemuqmpclient:main',
'shacache = slapos.shacache:main', 'shacache = slapos.shacache:main',
'slapbuilder = slapos.builder:main', 'slapbuilder = slapos.builder:main',
'slapcontainer = slapos.container:main', 'slapcontainer = slapos.container:main',
......
from datetime import datetime import argparse
import csv import csv
import feedparser import feedparser
import httplib # To avoid magic numbers
import io import io
import socket
import json import json
import time
import math import math
import httplib # To avoid magic numbers
import argparse
import os import os
import socket
import sys
import time
from datetime import datetime
from hashlib import sha512 from hashlib import sha512
from atomize import Entry from atomize import Entry
...@@ -19,6 +20,9 @@ from flask import abort ...@@ -19,6 +20,9 @@ from flask import abort
from flask import request from flask import request
app = Flask(__name__) app = Flask(__name__)
# csv entries can be very large, increase limit.
csv.field_size_limit(sys.maxsize)
@app.route('/get/<feed>') @app.route('/get/<feed>')
def get_feed(feed): def get_feed(feed):
global app global app
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import argparse import argparse
import csv import csv
import httplib import httplib
import os
import socket import socket
import subprocess import subprocess
import sys import sys
...@@ -32,28 +31,23 @@ def main(): ...@@ -32,28 +31,23 @@ def main():
args = parser.parse_args() args = parser.parse_args()
with open(os.devnull) as devnull: try:
command = subprocess.Popen(args.executable[0], content = subprocess.check_output(
stdin=subprocess.PIPE, args.executable[0],
stdout=devnull, stderr=subprocess.STDOUT
stderr=subprocess.PIPE, )
close_fds=True) exit_code = 0
command.stdin.flush() except subprocess.CalledProcessError as e:
command.stdin.close() content = e.output
exit_code = e.returncode
command_failed = (command.wait() != 0)
command_stderr = command.stderr.read() print content
if command_failed: content += ("\n<p>Failed with returncode <em>%d</em>.</p>"
content = ("<p>Failed with returncode <em>%d</em>.</p>" "<p>Output is: </p><pre>%s</pre>" % (
"<p>Standard error output is :</p><pre>%s</pre>") % ( exit_code,
command.poll(), content.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
command_stderr.replace('&', '&amp;')\ ))
.replace('<', '&lt;')\
.replace('>', '&gt;'),
)
else:
content = "<p>Everything went well.</p>"
with open(args.logfile[0], 'a') as file_: with open(args.logfile[0], 'a') as file_:
cvsfile = csv.writer(file_) cvsfile = csv.writer(file_)
...@@ -64,9 +58,8 @@ def main(): ...@@ -64,9 +58,8 @@ def main():
'slapos:%s' % uuid.uuid4(), 'slapos:%s' % uuid.uuid4(),
]) ])
if command_failed: if exit_code != 0:
sys.stderr.write('%s\n' % command_stderr) sys.exit(exit_code)
sys.exit(1)
print 'Fetching %s feed...' % args.feed_url[0] print 'Fetching %s feed...' % args.feed_url[0]
......
##############################################################################
#
# Copyright (c) 2013 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import argparse
import json
import os
import pprint
import socket
import time
def parseArgument():
"""
Very basic argument parser. Might blow up for anything else than
"./executable mysocket.sock stop/resume".
"""
parser = argparse.ArgumentParser()
parser.add_argument('--suspend', action='store_const', dest='action', const='suspend')
parser.add_argument('--resume', action='store_const', dest='action', const='resume')
parser.add_argument('--create-snapshot', action='store_const', dest='action', const='createSnapshot')
parser.add_argument('--create-internal-snapshot', action='store_const', dest='action', const='createInternalSnapshot')
parser.add_argument('--delete-internal-snapshot', action='store_const', dest='action', const='deleteInternalSnapshot')
parser.add_argument('--drive-backup', action='store_const', dest='action', const='driveBackup')
parser.add_argument('--query-commands', action='store_const', dest='action', const='queryCommands')
parser.add_argument('--socket', dest='unix_socket_location', required=True)
parser.add_argument('remainding_argument_list', nargs=argparse.REMAINDER)
args = parser.parse_args()
return args.unix_socket_location, args.action, args.remainding_argument_list
class QemuQMPWrapper(object):
"""
Small wrapper around Qemu's QMP to control a qemu VM.
See http://git.qemu.org/?p=qemu.git;a=blob;f=qmp-commands.hx for
QMP API definition.
"""
def __init__(self, unix_socket_location):
self.socket = self.connectToQemu(unix_socket_location)
self.capabilities()
@staticmethod
def connectToQemu(unix_socket_location):
"""
Create a socket, connect to qemu, be sure it answers, return socket.
"""
if not os.path.exists(unix_socket_location):
raise Exception('unix socket %s does not exist.' % unix_socket_location)
print 'Connecting to qemu...'
so = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
connected = False
while not connected:
try:
so.connect(unix_socket_location)
except socket.error:
time.sleep(1)
print 'Could not connect, retrying...'
else:
connected = True
so.recv(1024)
return so
def _send(self, message):
self.socket.send(json.dumps(message))
data = self.socket.recv(65535)
try:
return json.loads(data)
except ValueError:
print 'Wrong data: %s' % data
def _getVMStatus(self):
response = self._send({'execute': 'query-status'})
if response:
return self._send({'execute': 'query-status'})['return']['status']
else:
raise IOError('Empty answer')
def _waitForVMStatus(self, wanted_status):
while True:
try:
actual_status = self._getVMStatus()
if actual_status == wanted_status:
return
else:
print 'VM in %s status, wanting it to be %s, retrying...' % (
actual_status, wanted_status)
time.sleep(1)
except IOError:
print 'VM not ready, retrying...'
def capabilities(self):
print 'Asking for capabilities...'
self._send({'execute': 'qmp_capabilities'})
def suspend(self):
print 'Suspending VM...'
self._send({'execute': 'stop'})
self._waitForVMStatus('paused')
def resume(self):
print 'Resuming VM...'
self._send({'execute': 'cont'})
self._waitForVMStatus('running')
def _queryBlockJobs(self, device):
return self._send({'execute': 'query-block-jobs'})
def _getRunningJobList(self, device):
result = self._queryBlockJobs(device)
if result.get('return'):
return result['return']
else:
return
def driveBackup(self, backup_target, source_device='virtio0', sync_type='full'):
print 'Asking Qemu to perform backup to %s' % backup_target
# XXX: check for error
self._send({
'execute': 'drive-backup',
'arguments': {
'device': source_device,
'sync': sync_type,
'target': backup_target,
}
})
while self._getRunningJobList(backup_target):
print 'Job is not finished yet.'
time.sleep(20)
def createSnapshot(self, snapshot_file, device='virtio0'):
print self._send({
'execute': 'blockdev-snapshot-sync',
'arguments': {
'device': device,
'snapshot-file': snapshot_file,
}
})
def createInternalSnapshot(self, name=None, device='virtio0'):
if name is None:
name = int(time.time())
self._send({
'execute': 'blockdev-snapshot-internal-sync',
'arguments': {
'device': device,
'name': name,
}
})
def deleteInternalSnapshot(self, name, device='virtio0'):
self._send({
'execute': 'blockdev-snapshot-delete-internal-sync',
'arguments': {
'device': device,
'name': name,
}
})
def queryCommands(self):
pprint.pprint(self._send({'execute': 'query-commands'})['return'])
def main():
unix_socket_location, action, remainding_argument_list = parseArgument()
qemu_wrapper = QemuQMPWrapper(unix_socket_location)
if remainding_argument_list:
getattr(qemu_wrapper, action)(*remainding_argument_list)
else:
getattr(qemu_wrapper, action)()
if __name__ == '__main__':
main()
...@@ -3,10 +3,13 @@ service (usually deployed using "test" software type). ...@@ -3,10 +3,13 @@ service (usually deployed using "test" software type).
One entry point, if the service has been deployed One entry point, if the service has been deployed
from a "scalability" test node, so with special parameters, will automatically from a "scalability" test node, so with special parameters, will automatically
communicate to an ERP5 TestNode Master, and start the test. communicate when the instance is started to an ERP5 TestNode Master, and start the test.
The other entry point is supposed to be run manually from a simple "test" The other entry point, "bin/runStandaloneTest", is supposed to be run manually from a simple "test"
instance without special parameter, and will manually run the test. instance without special parameter (just request an instance of your software
release using the dedicated test software-type made for the occasion).
This is quite useful if you simply want to run the resiliency tests without having the whole
dedicated test infrastructure.
...@@ -44,3 +47,17 @@ For reference: How-to deploy the whole test system ...@@ -44,3 +47,17 @@ For reference: How-to deploy the whole test system
Note: the slapos nodes are currently deployed using slapos-in-partition. 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 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. 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"
...@@ -109,7 +109,9 @@ def runTestSuite(server_url, key_file, cert_file, ...@@ -109,7 +109,9 @@ def runTestSuite(server_url, key_file, cert_file,
3/ Resilience is done, wait XX seconds 3/ Resilience is done, wait XX seconds
4/ For each clone: do a takeover. Check that IPv6 of new main instance is different. Check, when doing a http request to the new VM that will fetch the stored random number, that the sent number is the same. 4/ For each clone: do a takeover. Check that IPv6 of new main instance is different. Check, when doing a http request to the new VM that will fetch the stored random number, that the sent number is the same.
Note: disk image is a simple debian with the following python code running at boot: Note: disk image is a simple debian with gunicorn and flask installed:
apt-get install python-setuptools; easy_install gunicorn flask
With the following python code running at boot in /root/number.py:
import os import os
...@@ -119,7 +121,7 @@ def runTestSuite(server_url, key_file, cert_file, ...@@ -119,7 +121,7 @@ def runTestSuite(server_url, key_file, cert_file,
storage = 'storage.txt' storage = 'storage.txt'
@app.route("/") @app.route("/")
def greeting_list(): # 'cause they are several greetings, and plural is forbidden. def greeting_list(): # 'cause there are several greetings, and plural is forbidden.
return "Hello World" return "Hello World"
@app.route("/get") @app.route("/get")
...@@ -128,13 +130,39 @@ def runTestSuite(server_url, key_file, cert_file, ...@@ -128,13 +130,39 @@ def runTestSuite(server_url, key_file, cert_file,
@app.route("/set") @app.route("/set")
def set(): def set():
if os.path.exists(storage): #if os.path.exists(storage):
abort(503) # abort(503)
open(storage, 'w').write(request.args['key']) open(storage, 'w').write(request.args['key'])
return "OK" return "OK"
if __name__ == "__main__": if __name__ == "__main__":
app.run(host='0.0.0.0', port=80) app.run(host='0.0.0.0', port=80)
Then create the boot script:
echo "cd /root; /usr/local/bin/gunicorn number:app -b 0.0.0.0:80 -D --error-logfile /root/error_log --access-logfile /root/access_log" > /etc/init.d/gunicorn-number
chmod +x /etc/init.d/gunicorn-number
update-rc.d gunicorn-number defaults
There also is a script that randomly generates I/O in /root/io.sh:
#!/bin/sh
# Randomly generates high I/O on disk. Goal is to write on disk so that
# it flushes at the same time that snapshot of disk image is done, to check if
# it doesn't corrupt image.
# Ayayo!
while [ 1 ]; do
dd if=/dev/urandom of=random count=2k
sync
sleep 0.2
done
Then create the boot script:
echo "/bin/sh /root/io.sh &" > /etc/init.d/io
chmod +x /etc/init.d/io
update-rc.d io defaults
""" """
slap = slapos.slap.slap() slap = slapos.slap.slap()
slap.initializeConnection(server_url, key_file, cert_file) slap.initializeConnection(server_url, key_file, cert_file)
...@@ -146,9 +174,6 @@ def runTestSuite(server_url, key_file, cert_file, ...@@ -146,9 +174,6 @@ def runTestSuite(server_url, key_file, cert_file,
ip = fetchMainInstanceIP(partition, software, kvm_rootinstance_name) ip = fetchMainInstanceIP(partition, software, kvm_rootinstance_name)
logger.info('KVM IP is %s.' % ip) logger.info('KVM IP is %s.' % ip)
key = setRandomKey(ip)
logger.info('Key set for test in current KVM: %s.' % key)
# In resilient stack, main instance (example with KVM) is named "kvm0", # In resilient stack, main instance (example with KVM) is named "kvm0",
# clones are named "kvm1", "kvm2", ... # clones are named "kvm1", "kvm2", ...
clone_count = int(total_instance_count) - 1 clone_count = int(total_instance_count) - 1
...@@ -158,6 +183,10 @@ def runTestSuite(server_url, key_file, cert_file, ...@@ -158,6 +183,10 @@ def runTestSuite(server_url, key_file, cert_file,
# Test each clone # Test each clone
while current_clone <= clone_count: while current_clone <= clone_count:
logger.info('Testing kvm%s.' % current_clone) logger.info('Testing kvm%s.' % current_clone)
key = setRandomKey(ip)
logger.info('Key set for test in current KVM: %s.' % key)
logger.info('Sleeping for %s seconds.' % SLEEP_TIME) logger.info('Sleeping for %s seconds.' % SLEEP_TIME)
time.sleep(SLEEP_TIME) time.sleep(SLEEP_TIME)
......
...@@ -32,6 +32,7 @@ import slapos.slap ...@@ -32,6 +32,7 @@ import slapos.slap
import logging import logging
import time import time
import os
class ResiliencyTestSuite(object): class ResiliencyTestSuite(object):
""" """
...@@ -104,6 +105,14 @@ class ResiliencyTestSuite(object): ...@@ -104,6 +105,14 @@ class ResiliencyTestSuite(object):
""" """
raise NotImplementedError('Overload me, I am an abstract method.') 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): def _getPartitionParameterDict(self):
""" """
...@@ -115,6 +124,7 @@ class ResiliencyTestSuite(object): ...@@ -115,6 +124,7 @@ class ResiliencyTestSuite(object):
software_type='resilient', software_type='resilient',
partition_reference=self.root_instance_name partition_reference=self.root_instance_name
).getConnectionParameterDict() ).getConnectionParameterDict()
self.deleteTimestamp()
def _returnNewInstanceParameter(self, parameter_key, old_parameter_value): def _returnNewInstanceParameter(self, parameter_key, old_parameter_value):
""" """
...@@ -126,8 +136,8 @@ class ResiliencyTestSuite(object): ...@@ -126,8 +136,8 @@ class ResiliencyTestSuite(object):
new_parameter_value = None new_parameter_value = None
while not new_parameter_value or new_parameter_value == 'None' or new_parameter_value == old_parameter_value: 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) 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) 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) self.logger.info('New parameter value of instance is %s' % new_parameter_value)
return new_parameter_value return new_parameter_value
......
...@@ -28,7 +28,10 @@ ...@@ -28,7 +28,10 @@
from .resiliencytestsuite import ResiliencyTestSuite from .resiliencytestsuite import ResiliencyTestSuite
import base64
import cookielib import cookielib
import json
from lxml import etree
import random import random
import string import string
import time import time
...@@ -63,6 +66,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -63,6 +66,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
300 300
) )
def _connectToSlaprunner(self, resource, data=None): def _connectToSlaprunner(self, resource, data=None):
""" """
Utility. Utility.
...@@ -81,41 +85,49 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -81,41 +85,49 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
def _login(self): def _login(self):
self.logger.debug('Logging in...') self.logger.debug('Logging in...')
self._connectToSlaprunner('doLogin', data='clogin=%s&cpwd=%s' % ( b64string = base64.encodestring('%s:%s' % (self.slaprunner_user, self.slaprunner_password))[:-1]
self.slaprunner_user, self._opener_director.addheaders = [('Authorization', 'Basic %s'%b64string)]
self.slaprunner_password)
)
def _retrieveInstanceLogFile(self): def _retrieveInstanceLogFile(self):
""" """
Store the logfile (=data) of the instance, check it is not empty nor it is Store the logfile (=data) of the instance, check it is not empty nor it is
html. html.
""" """
time.sleep(30)
data = self._connectToSlaprunner( data = self._connectToSlaprunner(
resource='fileBrowser', resource='getFileContent',
data='opt=9&filename=log.log&dir=instance_root%252Fslappart0%252Fvar%252Flog%252F' data="file=instance_root/slappart0/var/log/log.log"
) )
self.logger.info('Retrieved data are:\n%s' % data) try:
data = json.loads(data)['result']
if data.find('<') is not -1: self.logger.info('Retrieved data are:\n%s' % data)
raise IOError( except (ValueError, KeyError):
'Could not retrieve logfile content: retrieved content is html.' if data.find('<') is not -1:
) raise IOError(
if data.find('Could not load') is not -1: 'Could not retrieve logfile content: retrieved content is html.'
raise IOError( )
'Could not retrieve logfile content: server could not load the file.' if data.find('Could not load') is not -1:
) raise IOError(
if data.find('Hello') is -1: 'Could not retrieve logfile content: server could not load the file.'
raise IOError( )
'Could not retrieve logfile content: retrieve content does not match "Hello".' if data.find('Hello') is -1:
) raise IOError(
'Could not retrieve logfile content: retrieve content does not match "Hello".'
)
return data return data
def _waitForSoftwareBuild(self): def _waitForSoftwareBuild(self):
while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1: #while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1:
self.logger.info('Software release is still building. Sleeping...') # self.logger.info('Software release is still building. Sleeping...')
time.sleep(15) # time.sleep(15)
self.logger.info('Software Release has been built / is no longer building.') #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): def _buildSoftwareRelease(self):
self.logger.info('Building the Software Release...') self.logger.info('Building the Software Release...')
...@@ -158,6 +170,20 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -158,6 +170,20 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
data='path=workspace/slapos/software/%s/' % software_name data='path=workspace/slapos/software/%s/' % software_name
) )
def _getRcode(self):
#XXX-Nicolas: hardcoded url. Best way right now to automate the tests...
monitor_url = self.monitor_url + "?script=zero-knowledge%2Fsettings.cgi"
result = self._opener_director.open(monitor_url,
"password=passwordtochange")
if result.getcode() is not 200:
raise NotHttpOkException(result.getcode())
page = result.read().strip()
html = etree.HTML(page)
input = html.xpath("//input[@name='recovery-code']")
return input[0].get('value')
def generateData(self): def generateData(self):
self.slaprunner_password = ''.join( self.slaprunner_password = ''.join(
...@@ -180,7 +206,8 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -180,7 +206,8 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
parameter_dict = self._getPartitionParameterDict() parameter_dict = self._getPartitionParameterDict()
self.slaprunner_backend_url = parameter_dict['backend_url'] self.slaprunner_backend_url = parameter_dict['backend_url']
self.logger.info('backend_url is %s.' % self.slaprunner_backend_url) self.logger.info('backend_url is %s.' % self.slaprunner_backend_url)
slaprunner_recovery_code = parameter_dict['password_recovery_code'] self.monitor_url = parameter_dict['monitor_url']
slaprunner_recovery_code = self._getRcode()
self.logger.debug('Creating the slaprunner account...') self.logger.debug('Creating the slaprunner account...')
self._connectToSlaprunner( self._connectToSlaprunner(
...@@ -199,6 +226,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -199,6 +226,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
self._openSoftwareRelease('helloworld') self._openSoftwareRelease('helloworld')
self._buildSoftwareRelease() self._buildSoftwareRelease()
time.sleep(15)
self._deployInstance() self._deployInstance()
self.data = self._retrieveInstanceLogFile() self.data = self._retrieveInstanceLogFile()
...@@ -219,9 +247,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite): ...@@ -219,9 +247,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
) )
self._login() self._login()
self._waitForSoftwareBuild() self._waitForSoftwareBuild()
# XXX: in theory, it should be done automatically by slaprunner. time.sleep(15)
# In practice, it is still too dangerous for ERP5 instances.
self._deployInstance()
new_data = self._retrieveInstanceLogFile() new_data = self._retrieveInstanceLogFile()
if new_data == self.data: if new_data == self.data:
......
...@@ -6,52 +6,14 @@ import ConfigParser ...@@ -6,52 +6,14 @@ import ConfigParser
import datetime import datetime
import logging import logging
import logging.handlers import logging.handlers
from optparse import OptionParser, Option
import os import os
import slapos.runner.process from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys import sys
from slapos.runner.utils import runInstanceWithLock from slapos.runner.utils import runInstanceWithLock
from slapos.runner.views import *
TRUE_VALUES = (1, '1', True, 'true', 'True')
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]
class Config: class Config:
def __init__(self): def __init__(self):
...@@ -61,22 +23,18 @@ class Config: ...@@ -61,22 +23,18 @@ class Config:
self.logger = None self.logger = None
self.verbose = None self.verbose = None
def setConfig(self, option_dict, configuration_file_path): def setConfig(self):
""" """
Set options given by parameters. Set options given by parameters.
""" """
self.configuration_file_path = os.path.abspath(configuration_file_path) self.configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
# Set options parameters
for option, value in option_dict.__dict__.items():
setattr(self, option, value)
# Load configuration file # Load configuration file
configuration_parser = ConfigParser.SafeConfigParser() configuration_parser = ConfigParser.SafeConfigParser()
configuration_parser.read(configuration_file_path) configuration_parser.read(self.configuration_file_path)
# Merges the arguments and configuration
for section in ("slaprunner", "slapos", "slapproxy", "slapformat", for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient", "cloud9_IDE"): "sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section)) configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict: for key in configuration_dict:
if not getattr(self, key, None): if not getattr(self, key, None):
...@@ -88,17 +46,17 @@ class Config: ...@@ -88,17 +46,17 @@ class Config:
if self.console: if self.console:
self.logger.addHandler(logging.StreamHandler()) self.logger.addHandler(logging.StreamHandler())
if self.log_file: self.log_file = self.log_dir + '/slaprunner.log'
if not os.path.isdir(os.path.dirname(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 # fallback to console only if directory for logs does not exists and
# continue to run # continue to run
raise ValueError('Please create directory %r to store %r log file' % ( raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file)) os.path.dirname(self.log_file), self.log_file))
else: else:
file_handler = logging.FileHandler(self.log_file) file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler) self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file) self.logger.info('Configured logging to file %r' % self.log_file)
self.logger.info("Started.") self.logger.info("Started.")
self.logger.info(os.environ['PATH']) self.logger.info(os.environ['PATH'])
...@@ -107,30 +65,46 @@ class Config: ...@@ -107,30 +65,46 @@ class Config:
self.logger.debug("Verbose mode enabled.") self.logger.debug("Verbose mode enabled.")
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
def checkJSONConfig(config):
"""create a default json file with some parameters inside
if the file has never been created"""
json_file = os.path.join(config['etc_dir'], 'config.json')
if not os.path.exists(json_file):
params = {
'run_instance' : True,
'run_software' : True,
'max_run_instance' : 3,
'max_run_software' : 2
}
open(json_file, "w").write(json.dumps(params))
def run(): def run():
"Run default configuration." "Run default configuration."
usage = "usage: %s [options] CONFIGURATION_FILE" % sys.argv[0] # Parse arguments
config = Config()
try: config.setConfig()
# Parse arguments
config = Config()
config.setConfig(*Parser(usage=usage).check_args())
if os.getuid() == 0: if os.getuid() == 0:
# avoid mistakes (mainly in development mode) # avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.') 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
sys.exit(return_code)
serve(config)
def serve(config): def serve(config):
from views import app
from werkzeug.contrib.fixers import ProxyFix from werkzeug.contrib.fixers import ProxyFix
workdir = os.path.join(config.runner_workdir, 'project') workdir = os.path.join(config.runner_workdir, 'project')
software_link = os.path.join(config.runner_workdir, 'softwareLink') software_link = os.path.join(config.runner_workdir, 'softwareLink')
...@@ -145,14 +119,17 @@ def serve(config): ...@@ -145,14 +119,17 @@ def serve(config):
SECRET_KEY=os.urandom(24), SECRET_KEY=os.urandom(24),
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31), PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
) )
checkHtpasswd(app.config)
checkJSONConfig(app.config)
if not os.path.exists(workdir): if not os.path.exists(workdir):
os.mkdir(workdir) os.mkdir(workdir)
if not os.path.exists(software_link): if not os.path.exists(software_link):
os.mkdir(software_link) os.mkdir(software_link)
slapos.runner.process.setHandler() setHandler()
config.logger.info('Running slapgrid...') config.logger.info('Running slapgrid...')
runInstanceWithLock(app.config) if app.config['auto_deploy_instance'] in TRUE_VALUES:
runInstanceWithLock(app.config)
config.logger.info('Done.') config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app) 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): ...@@ -121,36 +121,40 @@ def getDiff(project):
result = safeResult(str(e)) result = safeResult(str(e))
return result 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): def gitPush(project):
"""Commit and Push changes for the specified repository """Push changes for the specified repository
Args: Args:
project: directory of the local repository project: directory of the local repository
msg: commit message""" msg: commit message"""
code = 0 code = 0
json = "" json = ""
undo_commit = False
try: try:
repo = Repo(project) #push changes to repo
if repo.is_dirty: current_branch = repo.active_branch.name
git = repo.git git.push('origin', current_branch)
current_branch = repo.active_branch.name code = 1
#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
except Exception as e: except Exception as e:
if undo_commit:
git.reset("HEAD~") # undo previous commit
json = safeResult(str(e)) json = safeResult(str(e))
return jsonify(code=code, result=json) return jsonify(code=code, result=json)
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
# vim: set et sts=2: # vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904 # 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 argparse
import ConfigParser import ConfigParser
import datetime import datetime
...@@ -12,11 +20,16 @@ import shutil ...@@ -12,11 +20,16 @@ import shutil
import time import time
import unittest import unittest
from slapos.runner.utils import (getProfilePath, getSession, isInstanceRunning, from slapos.runner.utils import (getProfilePath, getRcode,
isSoftwareRunning, startProxy) getSession, isInstanceRunning,
isSoftwareRunning, startProxy,
isSoftwareReleaseReady,
runSlapgridUntilSuccess,
getBuildAndRunParams, saveBuildAndRunParams)
from slapos.runner.process import killRunningProcess, isRunning from slapos.runner.process import killRunningProcess, isRunning
from slapos.runner import views from slapos.runner import views
import slapos.slap import slapos.slap
from slapos.htpasswd import HtpasswdFile
#Helpers #Helpers
...@@ -43,7 +56,7 @@ class Config: ...@@ -43,7 +56,7 @@ class Config:
# Merges the arguments and configuration # Merges the arguments and configuration
for section in ("slaprunner", "slapos", "slapproxy", "slapformat", for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient", "cloud9_IDE"): "sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section)) configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict: for key in configuration_dict:
if not getattr(self, key, None): if not getattr(self, key, None):
...@@ -57,13 +70,11 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -57,13 +70,11 @@ class SlaprunnerTestCase(unittest.TestCase):
views.app.config['TESTING'] = True views.app.config['TESTING'] = True
self.users = ["slapuser", "slappwd", "slaprunner@nexedi.com", "SlapOS web runner"] self.users = ["slapuser", "slappwd", "slaprunner@nexedi.com", "SlapOS web runner"]
self.updateUser = ["newslapuser", "newslappwd", "slaprunner@nexedi.com", "SlapOS web runner"] self.updateUser = ["newslapuser", "newslappwd", "slaprunner@nexedi.com", "SlapOS web runner"]
self.rcode = "41bf2657"
self.repo = 'http://git.erp5.org/repos/slapos.git' self.repo = 'http://git.erp5.org/repos/slapos.git'
self.software = "workspace/slapos/software/" # relative directory fo SR self.software = "workspace/slapos/software/" # relative directory fo SR
self.project = 'slapos' # Default project name self.project = 'slapos' # Default project name
self.template = 'template.cfg' self.template = 'template.cfg'
self.partitionPrefix = 'slappart' self.partitionPrefix = 'slappart'
self.slaposBuildout = "1.6.0-dev-SlapOS-010"
#create slaprunner configuration #create slaprunner configuration
config = Config() config = Config()
config.setConfig() config.setConfig()
...@@ -85,16 +96,29 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -85,16 +96,29 @@ class SlaprunnerTestCase(unittest.TestCase):
software_profile='software.cfg', software_profile='software.cfg',
SECRET_KEY="123456", SECRET_KEY="123456",
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31), 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() self.app = views.app.test_client()
self.app.config = views.app.config self.app.config = views.app.config
#Create password recover code #Create password recover code
with open(os.path.join(views.app.config['etc_dir'], '.rcode'), 'w') as rpwd: parser = ConfigParser.ConfigParser()
rpwd.write(self.rcode) parser.read(self.app.config['knowledge0_cfg'])
self.rcode = parser.get('public', 'recovery-code')
#Create config.json
json_file = os.path.join(views.app.config['etc_dir'], 'config.json')
if not os.path.exists(json_file):
params = {
'run_instance' : True,
'run_software' : True,
'max_run_instance' : 3,
'max_run_software' : 2
}
open(json_file, "w").write(json.dumps(params))
def tearDown(self): def tearDown(self):
"""Remove all test data""" """Remove all test data"""
os.unlink(os.path.join(self.app.config['etc_dir'], '.rcode'))
project = os.path.join(self.app.config['etc_dir'], '.project') project = os.path.join(self.app.config['etc_dir'], '.project')
users = os.path.join(self.app.config['etc_dir'], '.users') users = os.path.join(self.app.config['etc_dir'], '.users')
...@@ -110,7 +134,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -110,7 +134,6 @@ class SlaprunnerTestCase(unittest.TestCase):
shutil.rmtree(self.app.config['instance_root']) shutil.rmtree(self.app.config['instance_root'])
if os.path.exists(self.app.config['software_link']): if os.path.exists(self.app.config['software_link']):
shutil.rmtree(self.app.config['software_link']) shutil.rmtree(self.app.config['software_link'])
self.logout()
#Stop process #Stop process
killRunningProcess('slapproxy', recursive=True) killRunningProcess('slapproxy', recursive=True)
killRunningProcess('slapgrid-cp', recursive=True) killRunningProcess('slapgrid-cp', recursive=True)
...@@ -128,26 +151,11 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -128,26 +151,11 @@ class SlaprunnerTestCase(unittest.TestCase):
), ),
follow_redirects=True) 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): def setAccount(self):
"""Initialize user account and log user in""" """Initialize user account and log user in"""
response = loadJson(self.configAccount(self.users[0], self.users[1], response = loadJson(self.configAccount(self.users[0], self.users[1],
self.users[2], self.users[3], self.rcode)) self.users[2], self.users[3], self.rcode))
response2 = loadJson(self.login(self.users[0], self.users[1]))
self.assertEqual(response['result'], "") 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): def updateAccount(self, newaccount, rcode):
"""Helper for update user account data""" """Helper for update user account data"""
...@@ -186,8 +194,7 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -186,8 +194,7 @@ class SlaprunnerTestCase(unittest.TestCase):
sr += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n" 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 += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n" sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n\n" sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n zc.buildout\n\n"
sr += "[versions]\nzc.buildout = %s\n" % self.slaposBuildout
os.mkdir(testSoftware) os.mkdir(testSoftware)
open(os.path.join(testSoftware, self.app.config['software_profile']), open(os.path.join(testSoftware, self.app.config['software_profile']),
'w').write(sr) 'w').write(sr)
...@@ -216,13 +223,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -216,13 +223,6 @@ class SlaprunnerTestCase(unittest.TestCase):
"""Kill slapproxy process""" """Kill slapproxy process"""
killRunningProcess('slapproxy', recursive=True) 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): def test_configAccount(self):
"""For the first lauch of slaprunner user need do create first account""" """For the first lauch of slaprunner user need do create first account"""
result = self.configAccount(self.users[0], self.users[1], self.users[2], result = self.configAccount(self.users[0], self.users[1], self.users[2],
...@@ -232,34 +232,16 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -232,34 +232,16 @@ class SlaprunnerTestCase(unittest.TestCase):
account = getSession(self.app.config) account = getSession(self.app.config)
self.assertEqual(account, self.users) 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): def test_updateAccount(self):
"""test Update accound, this needs the user to log in""" """test Update accound, this needs the user to log in"""
self.setAccount() 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)) response = loadJson(self.updateAccount(self.updateUser, self.rcode))
self.assertEqual(response['code'], 1) self.assertEqual(response['code'], 1)
result = self.logout() encode = HtpasswdFile(htpasswd, False)
assert "<h2>Login to Slapos Web Runner</h2>" in result.data encode.update(self.updateUser[0], self.updateUser[1])
#retry login with new values assert self.updateUser[0] in open(htpasswd).read()
response = loadJson(self.login(self.updateUser[0], self.updateUser[1]))
self.assertEqual(response['result'], "")
#log out now!
self.logout()
def test_startProxy(self): def test_startProxy(self):
"""Test slapproxy""" """Test slapproxy"""
...@@ -301,7 +283,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -301,7 +283,6 @@ class SlaprunnerTestCase(unittest.TestCase):
), ),
follow_redirects=True)) follow_redirects=True))
self.assertEqual(response['result'], "") self.assertEqual(response['result'], "")
self.logout()
def test_createSR(self): def test_createSR(self):
"""Scenario 2: Create a new software release""" """Scenario 2: Create a new software release"""
...@@ -315,13 +296,10 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -315,13 +296,10 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertEqual(response['result'], "") self.assertEqual(response['result'], "")
currentSR = self.getCurrentSR() currentSR = self.getCurrentSR()
assert newSoftware in currentSR assert newSoftware in currentSR
self.logout()
def test_openSR(self): def test_openSR(self):
"""Scenario 3: Open software release""" """Scenario 3: Open software release"""
self.test_cloneProject() 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 software = os.path.join(self.software, 'drupal') # Drupal SR must exist in SR folder
response = loadJson(self.app.post('/setCurrentProject', response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software), data=dict(path=software),
...@@ -336,7 +314,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -336,7 +314,6 @@ class SlaprunnerTestCase(unittest.TestCase):
# newSoftware = os.path.join(self.software, 'slaprunner-test') # newSoftware = os.path.join(self.software, 'slaprunner-test')
self.proxyStatus(True) self.proxyStatus(True)
self.stopSlapproxy() self.stopSlapproxy()
self.logout()
def test_runSoftware(self): def test_runSoftware(self):
"""Scenario 4: CReate empty SR and save software.cfg file """Scenario 4: CReate empty SR and save software.cfg file
...@@ -345,8 +322,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -345,8 +322,6 @@ class SlaprunnerTestCase(unittest.TestCase):
#Call config account #Call config account
#call create software Release #call create software Release
self.test_createSR() self.test_createSR()
#Login
self.login(self.users[0], self.users[1])
newSoftware = self.getCurrentSR() newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n" softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n" softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
...@@ -362,10 +337,9 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -362,10 +337,9 @@ class SlaprunnerTestCase(unittest.TestCase):
# Compile software and wait until slapgrid ends # Compile software and wait until slapgrid ends
# this is supposed to use current SR # this is supposed to use current SR
response = loadJson(self.app.post('/runSoftwareProfile', while self.app.get('/isSRReady').data == "2":
data=dict(), time.sleep(2)
follow_redirects=True)) self.assertEqual(self.app.get('/isSRReady').data, "1")
self.assertTrue(response['result'])
self.assertTrue(os.path.exists(self.app.config['software_root'])) self.assertTrue(os.path.exists(self.app.config['software_root']))
self.assertTrue(os.path.exists(self.app.config['software_log'])) self.assertTrue(os.path.exists(self.app.config['software_log']))
assert "test-application" in open(self.app.config['software_log']).read() assert "test-application" in open(self.app.config['software_log']).read()
...@@ -376,7 +350,6 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -376,7 +350,6 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertTrue(os.path.exists(createdFile)) self.assertTrue(os.path.exists(createdFile))
self.proxyStatus(True) self.proxyStatus(True)
self.stopSlapproxy() self.stopSlapproxy()
self.logout()
def test_updateInstanceParameter(self): def test_updateInstanceParameter(self):
"""Scenarion 5: Update parameters of current sofware profile""" """Scenarion 5: Update parameters of current sofware profile"""
...@@ -419,24 +392,25 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -419,24 +392,25 @@ class SlaprunnerTestCase(unittest.TestCase):
response = loadJson(self.app.get('/getParameterXml/dict')) response = loadJson(self.app.get('/getParameterXml/dict'))
self.assertEqual(parameterDict, response['result']['instance']) self.assertEqual(parameterDict, response['result']['instance'])
self.stopSlapproxy() self.stopSlapproxy()
self.logout()
def test_requestInstance(self): def test_requestInstance(self):
"""Scenarion 6: request software instance""" """Scenarion 6: request software instance"""
self.test_updateInstanceParameter() self.test_updateInstanceParameter()
#Login
self.login(self.users[0], self.users[1])
self.proxyStatus(False, sleep_time=1) self.proxyStatus(False, sleep_time=1)
#run Software profile #run Software profile
response = loadJson(self.app.post('/runSoftwareProfile', response = loadJson(self.app.post('/runSoftwareProfile',
data=dict(), data=dict(),
follow_redirects=True)) follow_redirects=True))
self.assertTrue(response['result']) while self.app.get('/isSRReady').data == "2":
time.sleep(2)
self.assertEqual(self.app.get('/isSRReady').data, "1")
#run instance profile #run instance profile
response = loadJson(self.app.post('/runInstanceProfile', response = loadJson(self.app.post('/runInstanceProfile',
data=dict(), data=dict(),
follow_redirects=True)) follow_redirects=True))
self.assertTrue(response['result']) self.assertTrue(response['result'])
# lets some time to the Instance to be deployed
time.sleep(5)
#Check that all partitions has been created #Check that all partitions has been created
assert "create-file" in open(self.app.config['instance_log']).read() assert "create-file" in open(self.app.config['instance_log']).read()
instanceDir = os.listdir(self.app.config['instance_root']) instanceDir = os.listdir(self.app.config['instance_root'])
...@@ -453,7 +427,96 @@ class SlaprunnerTestCase(unittest.TestCase): ...@@ -453,7 +427,96 @@ class SlaprunnerTestCase(unittest.TestCase):
assert 'simple file' in open(createdFile).read() assert 'simple file' in open(createdFile).read()
self.proxyStatus(True) self.proxyStatus(True)
self.stopSlapproxy() 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 test_maximumRunOfSlapgrid(self):
"""Scenario 8: runSlapgridUntilSucces run until a defined maximum of time
slapgrid-sr and slapgrid-cp if it fails. It can also run only one or both
of them if it is defined so
We directly calls runSlapgridUntilSuccess, because we want
to test the return code of the function"""
# Installs a wrong buildout which will fail
MAX_RUN_SOFTWARE = getBuildAndRunParams(self.app.config)['max_run_software']
MAX_RUN_INSTANCE = getBuildAndRunParams(self.app.config)['max_run_instance']
self.test_createSR()
newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
softwareRelease += "[test-application]\nrecipe = slapos.cookbook:mkdirectory\n"
softwareRelease += "test = /root/test\n"
response = loadJson(self.app.post('/saveFileContent',
data=dict(file=newSoftware,
content=softwareRelease),
follow_redirects=True))
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, MAX_RUN_SOFTWARE)
# clean folders for other tests
workdir = os.path.join(self.app.config['runner_workdir'], 'project')
git_repo = os.path.join(workdir, 'slapos')
if os.path.exists(git_repo):
shutil.rmtree(git_repo)
# Installs a software which deploys, but fails while instanciating
# preparation
base = os.path.join(self.app.config['workspace'], 'slapos')
software = os.path.join(base, 'software')
testSoftware = os.path.join(software, 'slaprunner-test')
if not os.path.exists(testSoftware):
os.makedirs(testSoftware)
software_cfg = os.path.join(testSoftware, 'software.cfg')
instance_cfg = os.path.join(testSoftware, 'instance.cfg')
# software.cfg
softwareRelease = "[buildout]\n\nparts =\n failing-template\n\n"
softwareRelease += "[failing-template]\nrecipe = hexagonit.recipe.download\n"
softwareRelease += "url = %s\n" % (instance_cfg)
softwareRelease += "destination = ${buildout:directory}\n"
softwareRelease += "download-only = true\n"
open(software_cfg, 'w+').write(softwareRelease)
# instance.cfg
content = "[buildout]\n\nparts =\n fail\n"
content += "[fail]\nrecipe=plone.recipe.command\n"
content += "command = exit 1"
open(instance_cfg, 'w+').write(content)
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test')
project.close()
# Build and Run
parameters = getBuildAndRunParams(self.app.config)
parameters['run_instance'] = False
saveBuildAndRunParams(self.app.config, parameters)
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, 1)
parameters['run_instance'] = True
saveBuildAndRunParams(self.app.config, parameters)
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, (1, MAX_RUN_INSTANCE))
def main(): def main():
......
...@@ -48,7 +48,7 @@ th{ ...@@ -48,7 +48,7 @@ th{
} }
table.small th{padding: 4px;font-size: 16px;} table.small th{padding: 4px;font-size: 16px;}
textarea { textarea {
width:932px; width:932px;
font-family: 'Arial,Helvetica Neue',Tahoma,Helvetica,sans-serif; font-family: 'Arial,Helvetica Neue',Tahoma,Helvetica,sans-serif;
} }
...@@ -257,6 +257,10 @@ input[type="radio"], input[type="checkbox"]{ ...@@ -257,6 +257,10 @@ input[type="radio"], input[type="checkbox"]{
margin-bottom:10px; margin-bottom:10px;
} }
#commitmsg {
width:95%;
}
.message { .message {
color:#FF5500; color:#FF5500;
line-height:21px; line-height:21px;
...@@ -406,7 +410,7 @@ padding: 10px;height: 80px;padding-bottom:15px;} ...@@ -406,7 +410,7 @@ padding: 10px;height: 80px;padding-bottom:15px;}
} }
#code{ #code{
float: right; float: right;
width: 692px; width: 680px;
} }
#details_head{margin-bottom: 10px;} #details_head{margin-bottom: 10px;}
...@@ -629,6 +633,26 @@ a.no-right-border:focus{border-right:none} ...@@ -629,6 +633,26 @@ a.no-right-border:focus{border-right:none}
a.lshare img{ a.lshare img{
margin: 5px; margin: 5px;
} }
.box_header{
background: #E4E4E4;
width: 100%;
height: 30px;
padding-top: 2px;
text-indent: 5px;
color: #737373;
text-shadow: 0px 1px #F1F1F1;
border-bottom: 3px solid #6DB9DD;
}
.box_header li{float: left;border: 1px solid #AAB8C2;padding: 1px 5px 1px 0;margin-left: 5px;}
.box_header li:last-child{border:none}
.box_header li > span{cursor: pointer; height: 22px; display: block;line-height: 22px;font-weight: bold;padding: 1px;}
.box_header li:hover{border: 1px solid #57A1D6;background: #C7C7C7;}
.box_header li:last-child:hover{background:none; border: none}
.box_header li>a{font-weight: bold; font-size:15px;display: block;padding: 2px;}
.save_btn{background: url(../images/icon_save.png) center right no-repeat;width: 60px;}
.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;}
#editor, #editorViewer { #editor, #editorViewer {
margin: 0; margin: 0;
...@@ -772,4 +796,24 @@ padding:10px; font-size:14px; color:#03406A} ...@@ -772,4 +796,24 @@ padding:10px; font-size:14px; color:#03406A}
.ace_search{ .ace_search{
width: 350px; width: 350px;
max-width: 350px; max-width: 350px;
} }
\ No newline at end of file
.fullScreen .fullScreen-editor{
height: auto!important;
width: auto!important;
border: 0;
margin: 0;
position: fixed !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10000;
}
.fullScreen {
overflow: hidden;
}
/* ******************* */
#shellinabox{width:100%; min-height:530px;}
...@@ -45,6 +45,8 @@ $(document).ready(function () { ...@@ -45,6 +45,8 @@ $(document).ready(function () {
return false; return false;
} }
send = true; send = true;
var base_url = 'https://' + $("input#username").val() + ':'
+ $("input#password").val() + '@' + location.host
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: $SCRIPT_ROOT + ((hasAccount) ? '/updateAccount' : '/configAccount'), url: $SCRIPT_ROOT + ((hasAccount) ? '/updateAccount' : '/configAccount'),
...@@ -57,7 +59,8 @@ $(document).ready(function () { ...@@ -57,7 +59,8 @@ $(document).ready(function () {
}, },
success: function (data) { success: function (data) {
if (data.code === 1) { if (data.code === 1) {
window.location.href = $SCRIPT_ROOT + "/"; url = 'https://' + $("input#username").val() + ':' + $("input#password").val() + '@' + location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else { } else {
$("#error").Popup(data.result, {type: 'error', duration: 5000}); $("#error").Popup(data.result, {type: 'error', duration: 5000});
} }
...@@ -67,4 +70,68 @@ $(document).ready(function () { ...@@ -67,4 +70,68 @@ $(document).ready(function () {
}); });
return false; return false;
}); });
$("#save").click(function () {
if (send) {
return false;
}
send = true;
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/updateBuildAndRunConfig',
data: {
run_instance: $("input#run_instance").is(':checked'),
run_software: $("input#run_software").is(':checked'),
max_run_instance: $("input#max_run_instance").val(),
max_run_software: $("input#max_run_software").val()
},
success: function (data) {
if (data.code === 1) {
$("#error").Popup(data.result, {type: 'alert', duration: 5000});
} else {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
}
send = false;
},
error: function () { send = false; }
});
return false;
});
$("#add_user").click(function () {
if ($("input#new_username").val() === "" || !$("input#new_username").val().match(/^[\w\d\._\-]+$/)) {
$("#error").Popup("Invalid user name. Please check it!", {type: 'alert', duration: 3000});
return false;
}
if (!$("input#new_rcode").val().match(/^[\w\d]+$/)) {
$("#error").Popup("Please enter your password recovery code.", {type: 'alert', duration: 3000});
return false;
}
if (send) {
return false;
}
send = true;
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/addUser',
data: {
username: $("input#new_username").val(),
password: $("input#new_password").val(),
rcode: $("input#new_rcode").val(),
},
success: function (data) {
if (data.code === 1) {
$("#error").Popup(data.result, {type: 'info', duration: 5000});
} else if (data.code === 0) {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
} else {
$("#error").Popup(data.result, {type: 'alert', duration: 5000});
}
send = false;
$("input#new_username").val('');
$("input#new_password").val('');
$("input#new_rcode").val('');
},
error: function () { send = false; }
});
return false;
});
}); });
...@@ -104,3 +104,4 @@ function bindRemove() { ...@@ -104,3 +104,4 @@ function bindRemove() {
} }
}); });
}(jQuery, document, this)); }(jQuery, document, this));
...@@ -27,7 +27,9 @@ $(document).ready(function () { ...@@ -27,7 +27,9 @@ $(document).ready(function () {
$("#login").removeClass("button").addClass("dsblebutton"); $("#login").removeClass("button").addClass("dsblebutton");
$.post(url, param, function (data) { $.post(url, param, function (data) {
if (data.code === 1) { if (data.code === 1) {
window.location.href = $SCRIPT_ROOT + '/'; url = 'https://' + param.clogin + ':' + param.cpwd + '@'
+ location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else { } else {
$("#error").Popup(data.result, {type: 'alert', duration: 3000}); $("#error").Popup(data.result, {type: 'alert', duration: 3000});
} }
......
...@@ -89,12 +89,6 @@ function getRunningState() { ...@@ -89,12 +89,6 @@ function getRunningState() {
} }
}).error(function () { }).error(function () {
clearAll(false); clearAll(false);
}).complete(function () {
if (running) {
setTimeout(function () {
getRunningState();
}, speed);
}
}); });
} }
...@@ -147,7 +141,8 @@ function bindRun() { ...@@ -147,7 +141,8 @@ function bindRun() {
} else { } else {
if (!isRunning()) { if (!isRunning()) {
setCookie("slapgridCMD", "Instance"); setCookie("slapgridCMD", "Instance");
window.location.href = $SCRIPT_ROOT + "/viewLog"; if (window.location.pathname === "/viewLog")
window.location.href = $SCRIPT_ROOT + "/viewLog";
} }
} }
return false; return false;
...@@ -173,6 +168,13 @@ function updateStatus(elt, val) { ...@@ -173,6 +168,13 @@ function updateStatus(elt, val) {
$(src).children('p').text("Processing"); $(src).children('p').text("Processing");
break; break;
} }
// in case of failure
if ($("#salpgridLog").text().indexOf("Failed to run buildout profile") !== -1) {
var src = '#' + elt + '_run_state', value = 'state_' + "stopped";
$(src).removeClass();
$(src).addClass(value);
$(src).children('p').text("Buildout Failed");
}
} }
function setRunningState(data) { function setRunningState(data) {
...@@ -197,6 +199,13 @@ function setRunningState(data) { ...@@ -197,6 +199,13 @@ function setRunningState(data) {
$("#softrun").addClass('slapos_stop'); $("#softrun").addClass('slapos_stop');
$("#running img").before('<p id="running_info" class="instance">Running instance...</p>'); $("#running img").before('<p id="running_info" class="instance">Running instance...</p>');
} }
if (processType === "Software") {
running = false;
$("#running_info").remove();
$("#softrun").addClass('slapos_run');
$("#softrun").removeClass('slapos_stop');
$("#instrun").click();
}
processType = "Instance"; processType = "Instance";
} }
} }
...@@ -215,6 +224,7 @@ function setRunningState(data) { ...@@ -215,6 +224,7 @@ function setRunningState(data) {
$("#slapswitch").text('Access application'); $("#slapswitch").text('Access application');
} }
$("#running").hide(); $("#running").hide();
$("#running_info").remove();
running = false; //nothing is currently running running = false; //nothing is currently running
$("#softrun").removeClass('slapos_stop'); $("#softrun").removeClass('slapos_stop');
$("#softrun").addClass('slapos_run'); $("#softrun").addClass('slapos_run');
...@@ -238,11 +248,14 @@ function runProcess(urlfor, data) { ...@@ -238,11 +248,14 @@ function runProcess(urlfor, data) {
if ( $("#running_info").children('span').length > 0 ) { if ( $("#running_info").children('span').length > 0 ) {
$("#running_info").children('p').remove(); $("#running_info").children('p').remove();
} }
setRunningState(data);
setTimeout(getRunningState, 6000);
} }
} }
setInterval('GetStateRegularly()', 800);
function GetStateRegularly() {
getRunningState();
}
function checkSavedCmd() { function checkSavedCmd() {
"use strict"; "use strict";
var result = getCookie("slapgridCMD"); var result = getCookie("slapgridCMD");
......
...@@ -2,6 +2,13 @@ ...@@ -2,6 +2,13 @@
/*global $, document, $SCRIPT_ROOT */ /*global $, document, $SCRIPT_ROOT */
/* vim: set et sts=4: */ /* vim: set et sts=4: */
$.valHooks.textarea = {
get: function (elem) {
"use strict";
return elem.value.replace(/\r?\n/g, "\r\n");
}
};
$(document).ready(function () { $(document).ready(function () {
"use strict"; "use strict";
...@@ -23,7 +30,7 @@ $(document).ready(function () { ...@@ -23,7 +30,7 @@ $(document).ready(function () {
var project = $("#project").val(), var project = $("#project").val(),
urldata = $("input#workdir").val() + "/" + project; urldata = $("input#workdir").val() + "/" + project;
$("#status").empty(); $("#status").empty();
$("#push").hide(); $("#commit").hide();
$("#flash").empty(); $("#flash").empty();
if (project === "") { if (project === "") {
$("#status").append("<h2>Please select one project...</h2><br/><br/>"); $("#status").append("<h2>Please select one project...</h2><br/><br/>");
...@@ -48,7 +55,7 @@ $(document).ready(function () { ...@@ -48,7 +55,7 @@ $(document).ready(function () {
//alert(message); //alert(message);
$("#status").append("<p>" + message + "</p>"); $("#status").append("<p>" + message + "</p>");
if (data.dirty) { if (data.dirty) {
$("#push").show(); $("#commit").show();
$("#status").append("<br/><h2>Display Diff for current Project</h2>"); $("#status").append("<br/><h2>Display Diff for current Project</h2>");
$("#status").append("<p style='font-size:15px;'>You have changes in your project." + $("#status").append("<p style='font-size:15px;'>You have changes in your project." +
" <a href='#' id='viewdiff'" " <a href='#' id='viewdiff'"
...@@ -162,9 +169,9 @@ $(document).ready(function () { ...@@ -162,9 +169,9 @@ $(document).ready(function () {
checkout("0"); checkout("0");
return false; return false;
}); });
$("#commit").click(function () { $("#commitbutton").click(function () {
if ($("input#commitmsg").val() === "" || 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}); $("#error").Popup("Please Enter the commit message", {type: 'alert', duration: 3000});
return false; return false;
} }
...@@ -174,12 +181,12 @@ $(document).ready(function () { ...@@ -174,12 +181,12 @@ $(document).ready(function () {
send = true; send = true;
var project = $("#project").val(); var project = $("#project").val();
$("#imgwaitting").fadeIn('normal'); $("#imgwaitting").fadeIn('normal');
$("#commit").empty(); //$("#commit").empty();
$("#commit").attr("value", "Wait..."); $("#commitbbutton").attr("value", "Wait...");
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: $SCRIPT_ROOT + '/pushProjectFiles', url: $SCRIPT_ROOT + '/commitProjectFiles',
data: {project: $("input#workdir").val() + "/" + project, msg: $("input#commitmsg").val()}, data: {project: $("input#workdir").val() + "/" + project, msg: $("textarea#commitmsg").val()},
success: function (data) { success: function (data) {
if (data.code === 1) { if (data.code === 1) {
if (data.result !== "") { if (data.result !== "") {
...@@ -187,19 +194,46 @@ $(document).ready(function () { ...@@ -187,19 +194,46 @@ $(document).ready(function () {
} else { } else {
$("#error").Popup("Commit done!", {type: 'confirm', duration: 3000}); $("#error").Popup("Commit done!", {type: 'confirm', duration: 3000});
} }
$("#commit").hide();
gitStatus(); gitStatus();
} else { } else {
$("#error").Popup(data.result, {type: 'error'}); $("#error").Popup(data.result, {type: 'error'});
} }
$("#imgwaitting").hide(); $("#imgwaitting").hide();
$("#commit").empty(); $("#commitmsg").empty();
$("#commit").attr("value", "Commit"); $("#commitbutton").attr("value", "Commit");
send = false; send = false;
} }
}); });
return 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 (){ $("#pullbranch").click(function (){
if (send){ if (send){
......
...@@ -15,7 +15,7 @@ $(document).ready(function () { ...@@ -15,7 +15,7 @@ $(document).ready(function () {
softwareDisplay = true, softwareDisplay = true,
projectDir = $("input#project").val(), projectDir = $("input#project").val(),
workdir = $("input#workdir").val(), workdir = $("input#workdir").val(),
currentProject = workdir + "/" + projectDir.replace(workdir, "").split('/')[1], currentProject = "workspace/" + projectDir.replace(workdir, "").split('/')[1],
send = false, send = false,
edit = false, edit = false,
ajaxResult = false, ajaxResult = false,
...@@ -31,6 +31,7 @@ $(document).ready(function () { ...@@ -31,6 +31,7 @@ $(document).ready(function () {
var MIN_TABITEM_WIDTH = 61; //The minimum size of tabItem var MIN_TABITEM_WIDTH = 61; //The minimum size of tabItem
var MAX_TAB_NUMBER = 10; //The maximum number of tab that could be opened var MAX_TAB_NUMBER = 10; //The maximum number of tab that could be opened
function alertStatus (jqXHR) { function alertStatus (jqXHR) {
if (jqXHR.status == 404) { if (jqXHR.status == 404) {
$("#error").Popup("Requested page not found. [404]", {type: 'error'}); $("#error").Popup("Requested page not found. [404]", {type: 'error'});
...@@ -264,15 +265,26 @@ $(document).ready(function () { ...@@ -264,15 +265,26 @@ $(document).ready(function () {
editorlist[activeToken].changed = true; editorlist[activeToken].changed = true;
$(activeSpan).html("*" + $(activeSpan).html()); $(activeSpan).html("*" + $(activeSpan).html());
} }
if (!beforeunload_warning_set) {
window.onbeforeunload = function() { return "You have unsaved changes"; };
beforeunload_warning_set = true;
}
}); });
editor.commands.addCommand({ editor.commands.addCommand({
name: 'myCommand', name: 'SaveText',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) { exec: function(editor) {
$("#save").click(); $("#save").click();
}, },
readOnly: false // false if this command should not apply in readOnly mode readOnly: false // false if this command should not apply in readOnly mode
}); });
editor.commands.addCommand({
name: 'Fullscreen',
bindKey: {win: 'Ctrl-E', mac: 'Command-E'},
exec: function(editor) {
$("#fullscreen").click();
}
});
} }
function getCurrentEditor () { function getCurrentEditor () {
...@@ -302,7 +314,7 @@ $(document).ready(function () { ...@@ -302,7 +314,7 @@ $(document).ready(function () {
function switchContent() { function switchContent() {
if (!softwareDisplay) { if (!softwareDisplay) {
$("span.swith_btn").empty(); $("span.swith_btn").empty();
$("span.swith_btn").append("Workspace"); $("span.swith_btn").append("Working dir");
$('#fileTreeFull').show(); $('#fileTreeFull').show();
$('#fileTree').hide(); $('#fileTree').hide();
} else { } else {
...@@ -814,7 +826,7 @@ $(document).ready(function () { ...@@ -814,7 +826,7 @@ $(document).ready(function () {
modelist = require("ace/ext/modelist"); modelist = require("ace/ext/modelist");
config = require("ace/config"); config = require("ace/config");
initTree('#fileTree', currentProject, 'pfolder'); initTree('#fileTree', currentProject, 'pfolder');
initTree('#fileTreeFull', 'workspace'); initTree('#fileTreeFull', 'runner_workdir');
//bindContextMenu('#fileTree'); //bindContextMenu('#fileTree');
$("#info").append("Current work tree: " + base_path()); $("#info").append("Current work tree: " + base_path());
...@@ -827,6 +839,8 @@ $(document).ready(function () { ...@@ -827,6 +839,8 @@ $(document).ready(function () {
if ($("#tabControl div.item").length === 0) { if ($("#tabControl div.item").length === 0) {
return false; return false;
} }
beforeunload_warning_set = false;
window.onbeforeunload = function() { return; };
var hash = getActiveToken(); var hash = getActiveToken();
if (editorlist[hash].busy) { if (editorlist[hash].busy) {
return false; return false;
...@@ -901,6 +915,17 @@ $(document).ready(function () { ...@@ -901,6 +915,17 @@ $(document).ready(function () {
$("#option").click(); $("#option").click();
return false; return false;
}); });
$("a#addflist").click(function(){
addToFavourite(current_file);
$("#option").click();
return false;
});
$("a#find").click(function(){
config.loadModule("ace/ext/searchbox", function(e) {e.Search(editor)});
$("#option").click();
return false;
});
$("a#find").click(function () { $("a#find").click(function () {
if ($("#tabControl div.item").length === 0) { if ($("#tabControl div.item").length === 0) {
...@@ -924,4 +949,10 @@ $(document).ready(function () { ...@@ -924,4 +949,10 @@ $(document).ready(function () {
return false; return false;
}); });
$("#fullscreen").click(function(){
$("body").toggleClass("fullScreen");
$("#editor").toggleClass("fullScreen-editor");
editor.resize();
});
}); });
...@@ -6,8 +6,19 @@ ...@@ -6,8 +6,19 @@
$(document).ready(function () { $(document).ready(function () {
"use strict"; "use strict";
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
}
// Current_log is not used for auto displaying mode, only for manual reload of log file!!! // Current_log is not used for auto displaying mode, only for manual reload of log file!!!
var current_log = 'instance.log', var current_log = (getQueryVariable("logfile") !== undefined)? getQueryVariable("logfile") : "instance.log",
sending, sending,
state, state,
selectedFile = "", selectedFile = "",
......
...@@ -11,7 +11,10 @@ ...@@ -11,7 +11,10 @@
<div id="tabContainer"> <div id="tabContainer">
<ul> <ul>
<li><a href="#tab1" class="active">Your personal information</a></li> <li><a href="#tab1" class="active">Your personal information</a></li>
<!--<li><a href="#tab2">Configurations</a></li>--> {% if params %}
<li><a href="#tab2">Build & Run configuration</a></li>
<li><a href="#tab3">Add user</a></li>
{% endif %}
</ul><!-- //Tab buttons --> </ul><!-- //Tab buttons -->
<div class="tabDetails"> <div class="tabDetails">
<div id="tab1" class="tabContents"> <div id="tab1" class="tabContents">
...@@ -33,7 +36,7 @@ ...@@ -33,7 +36,7 @@
<input type='password' name='cpassword' id='cpassword' value=''/> <input type='password' name='cpassword' id='cpassword' value=''/>
<div class='clear'></div> <div class='clear'></div>
<br/> <br/>
<label for="rcode">Password Recover code:</label> <label for="rcode">Password Recovery code:</label>
<input type='password' name='rcode' id='rcode' value=''/> <input type='password' name='rcode' id='rcode' value=''/>
<span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span> <span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span>
<div class='clear'></div> <div class='clear'></div>
...@@ -45,8 +48,52 @@ ...@@ -45,8 +48,52 @@
<input type="hidden" name="hasAccount" id="hasAccount" value="{{name}}"/> <input type="hidden" name="hasAccount" id="hasAccount" value="{{name}}"/>
</form> </form>
</div> </div>
<!--<div id="tab2" class="tabContents"> {% if params %}
</div>--> <div id="tab2" class="tabContents">
<form class="slapgrid">
<div class='form'>
<h2>Which one do you want to run ?</h2>
<label for="run_software">Build :</label>
<input type='checkbox' name='run_software' id='run_software' value='run_software' {% if params.run_software -%}checked{% endif %}>
<div class='clear'></div>
<label for="run_instance">Run :</label>
<input type='checkbox' name='run_instance' id='run_instance' value='run_instance' {% if params.run_instance -%}checked{% endif %}>
<div class='clear'></div>
<h2>How many tries ?</h2>
<label for="max_run_software">Max times for Build :</label>
<input type='text' name='max_run_software' id='max_run_software' value="{{params.max_run_software}}">
<div class='clear'></div>
<label for="max_run_instance">Max times for Run :</label>
<input type='text' name='max_run_instance' id='max_run_instance' value="{{params.max_run_instance}}">
<div class='clear'></div>
<br/>
<label></label>
<input type="submit" name="save" id ="save" value="Save config" class="button"/>
<div class='clear'></div>
</div>
</form>
</div>
<div id="tab3" class="tabContents">
<form class="slapgrid">
<div class='form'>
<label for="username">New user name :</label>
<input type='text' name='username' id='new_username'/>
<div class='clear'></div>
<label for="password">New password :</label>
<input type='password' name='password' id='new_password'/>
<div class='clear'></div>
<label for="new_rcode">Password Recovery code:</label>
<input type='password' name='new_rcode' id='new_rcode' value=''/>
<span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span>
<div class='clear'></div>
<br/>
<label></label>
<input type="submit" name="add_user" id="add_user" value="Add new user" class="button"/>
<div class='clear'></div>
</div>
</form>
</div>
{% endif %}
</div> </div>
</div> </div>
{% if username %}<div id="file_info" class="file_info">leave passwords blank to preserve your current password... {% if username %}<div id="file_info" class="file_info">leave passwords blank to preserve your current password...
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
<li><a href="#tab2">Connection Information</a></li> <li><a href="#tab2">Connection Information</a></li>
<li><a href="#tab3" id="parameterTab">Parameters</a></li> <li><a href="#tab3" id="parameterTab">Parameters</a></li>
<li><a href="#tab4" id="instancetabfiles">Partitions Content</a></li> <li><a href="#tab4" id="instancetabfiles">Partitions Content</a></li>
<li><a href="#tab5">Monitoring</a></li>
</ul><!-- //Tab buttons --> </ul><!-- //Tab buttons -->
<div class="tabDetails"> <div class="tabDetails">
<div id="tab1" class="tabContents"> <div id="tab1" class="tabContents">
...@@ -132,6 +133,16 @@ ...@@ -132,6 +133,16 @@
</h2> </h2>
{%endif%} {%endif%}
</div><!-- end tab4 --> </div><!-- end tab4 -->
<div id="tab5" class="tabContents">
<h2>Monitoring interface</h2>
<div class="clear"></div>
<p>By clicking on the next link, you can access to the monitoring interface of the instance running inside the webrunner.</p>
<div class="clear"></div>
<p><b>Notice</b> that you have to extend the stack monitor in your software release (as explained <a href="http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/stack/monitor/README.txt?js=1">here</a>) for this weblink to work.
<div class="clear"></div>
<p><b><a href="{{ g.instance_monitoring_url }}" target="_blank">{{ g.instance_monitoring_url }}</a></b></p>
<div class="clear"></div>
</div><!-- end tab5 -->
</div> </div>
</div> </div>
<!-- This contains the hidden content for inline calls --> <!-- This contains the hidden content for inline calls -->
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
{% if request.path != '/login' %} {% if request.path != '/login' %}
<script src="{{ url_for('static', filename='js/ace/ace.js') }}" type="text/javascript" charset="utf-8"></script> <script src="{{ url_for('static', filename='js/ace/ace.js') }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/ace/ext-modelist.js') }}" type="text/javascript" charset="utf-8"></script> <script src="{{ url_for('static', filename='js/ace/ext-modelist.js') }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/ace/ext-language_tools.js') }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/scripts/process.js') }}" type="text/javascript" charset="utf-8"></script> <script src="{{ url_for('static', filename='js/scripts/process.js') }}" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
...@@ -66,8 +67,6 @@ ...@@ -66,8 +67,6 @@
<div class="block_header"> <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> <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> <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> <h2 class="info">{% block title %}{% endblock %} - {{session.title}}</h2>
<div class="run"> <div class="run">
<div id="running" style="display:none"> <div id="running" style="display:none">
...@@ -81,7 +80,7 @@ ...@@ -81,7 +80,7 @@
<li><a href="{{ url_for('editCurrentProject') }}">Editor</a></li> <li><a href="{{ url_for('editCurrentProject') }}">Editor</a></li>
<li><a href="{{ url_for('inspectInstance') }}">Services</a></li> <li><a href="{{ url_for('inspectInstance') }}">Services</a></li>
<li><a href="{{ url_for('viewLog') }}">Logs</a></li> <li><a href="{{ url_for('viewLog') }}">Logs</a></li>
<li><a href="{{ url_for('viewLog') }}">Terminal</a></li> <li><a href="{{ url_for('shell') }}">Terminal</a></li>
<li><a href="{{ url_for('manageRepository')}}#tab2">Git</a></li> <li><a href="{{ url_for('manageRepository')}}#tab2">Git</a></li>
<li class='right_menu main_menu'><a href="#"></a> <li class='right_menu main_menu'><a href="#"></a>
<ul> <ul>
...@@ -94,9 +93,7 @@ ...@@ -94,9 +93,7 @@
<li><a href="{{ url_for('runInstanceProfile') }}" id="instrun">Redeploy Services</a></li> <li><a href="{{ url_for('runInstanceProfile') }}" id="instrun">Redeploy Services</a></li>
<li class='sep'></li> <li class='sep'></li>
<li><a href="{{ url_for('browseWorkspace') }}">Browse Workspace</a></li> <li><a href="{{ url_for('browseWorkspace') }}">Browse Workspace</a></li>
<li><a href="{{ url_for('inspectSoftware') }}">My Softwares Releases</a></li> <li><a href="{{ url_for('inspectSoftware') }}">My Software Releases</a></li>
<li class='sep'></li>
<li><a href="{{ url_for('dologout') }}">Log out</a></li>
</ul> </ul>
</li> </li>
<li class='right_menu slapos_run' id="softrun"><a href="{{ url_for('runSoftwareProfile') }}"></a> <li class='right_menu slapos_run' id="softrun"><a href="{{ url_for('runSoftwareProfile') }}"></a>
......
{% extends "layout.html" %}
{% block body %}
<iframe id="shellinabox" src="/shellinabox"></iframe>
{% endblock %}
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<!-- Definition of context menu --> <!-- Definition of context menu -->
<ul id="fileTreeMenu" class="contextMenu"> <ul id="fileTreeMenu" class="contextMenu">
<li class="edit"><a href="#edit">Edit</a></li> <li class="edit"><a href="#edit">Edit</a></li>
<li class="view"><a href="#view">View this file</a></li> <li class="view"><a href="#view">Open in popup</a></li>
<li class="rename separator"><a href="#rename">Rename</a></li> <li class="rename separator"><a href="#rename">Rename</a></li>
<li class="delete "><a href="#delete">Delete</a></li> <li class="delete "><a href="#delete">Delete</a></li>
<li class="refresh separator"><a href="#refresh">Refresh</a></li> <li class="refresh separator"><a href="#refresh">Refresh</a></li>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
<ul class="inline"> <ul class="inline">
<li><a id='getmd5' href="#">Get or Update md5sum</a></li> <li><a id='getmd5' href="#">Get or Update md5sum</a></li>
<li><a id='addflist' href="#">Add to favourites</a></li> <li><a id='addflist' href="#">Add to favourites</a></li>
<li><a id='addflist' href="#">Full screen</a></li> <li><a id='fullscreen' title="Show Editor in Fullscreen. Hint: Use Ctrl+E" href="#">Full screen &nbsp;&nbsp;[Ctrl+E]</a></li>
<li><a id='find' href="#">Find in file &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Ctrl+F]</a></li> <li><a id='find' href="#">Find in file &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Ctrl+F]</a></li>
<li><a id='replace' href="#">Replace in file &nbsp;&nbsp;[Ctrl+H]</a></li> <li><a id='replace' href="#">Replace in file &nbsp;&nbsp;[Ctrl+H]</a></li>
</ul> </ul>
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
<p>Processing</p> <p>Processing</p>
<div class="clear"></div> <div class="clear"></div>
</div> </div>
<p>SlapOS rebuild your software from source, allowing you to easily patch or add any free software. <a href="#">Lean how!</a></p> <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> </div>
<h2 class="instance">Running State</h2> <h2 class="instance">Running State</h2>
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
<p>Waiting for starting</p> <p>Waiting for starting</p>
<div class="clear"></div> <div class="clear"></div>
</div> </div>
<p>SlapOS configure your running environment to match your needs. <a href="#">Lean how!</a></p> <p>SlapOS configure your running environment to match your needs. <a href="{{ url_for('viewLog', logfile='instance.log') }}">Learn how!</a></p>
</div> </div>
<div class="separator"></div> <div class="separator"></div>
......
...@@ -2,12 +2,15 @@ ...@@ -2,12 +2,15 @@
# vim: set et sts=2: # vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,W0141,W0142 # pylint: disable-msg=W0311,C0301,C0103,C0111,W0141,W0142
import ConfigParser
import json
import logging import logging
import md5 import md5
import multiprocessing import multiprocessing
import os
import re import re
import shutil import shutil
import os import thread
import time import time
import urllib import urllib
from xml.dom import minidom from xml.dom import minidom
...@@ -15,6 +18,8 @@ from xml.dom import minidom ...@@ -15,6 +18,8 @@ from xml.dom import minidom
import xml_marshaller import xml_marshaller
from flask import jsonify from flask import jsonify
from slapos.runner.gittools import cloneRepo
from slapos.runner.process import Popen, isRunning, killRunningProcess from slapos.runner.process import Popen, isRunning, killRunningProcess
from slapos.htpasswd import HtpasswdFile from slapos.htpasswd import HtpasswdFile
import slapos.slap import slapos.slap
...@@ -23,6 +28,7 @@ import slapos.slap ...@@ -23,6 +28,7 @@ import slapos.slap
logger = logging.getLogger('werkzeug') logger = logging.getLogger('werkzeug')
TRUE_VALUES = (1, '1', True, 'true', 'True')
html_escape_table = { html_escape_table = {
"&": "&amp;", "&": "&amp;",
...@@ -32,6 +38,19 @@ html_escape_table = { ...@@ -32,6 +38,19 @@ html_escape_table = {
"<": "&lt;", "<": "&lt;",
} }
def getBuildAndRunParams(config):
json_file = os.path.join(config['etc_dir'], 'config.json')
json_params = json.load(open(json_file))
return json_params
def saveBuildAndRunParams(config, params):
"""XXX-Nico parameters have to be correct.
Works like that because this function do not care
about how you got the parameters"""
json_file = os.path.join(config['etc_dir'], 'config.json')
open(json_file, "w").write(json.dumps(params))
def html_escape(text): def html_escape(text):
"""Produce entities within text.""" """Produce entities within text."""
...@@ -94,6 +113,25 @@ def saveSession(config, account): ...@@ -94,6 +113,25 @@ def saveSession(config, account):
return str(e) return str(e)
def getRcode(config):
parser = ConfigParser.ConfigParser()
try:
parser.read(config['knowledge0_cfg'])
return parser.get('public', 'recovery-code')
except (ConfigParser.NoSectionError, IOError) as e:
return None
def createNewUser(config, name, passwd):
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(htpasswdfile):
htpasswd = HtpasswdFile(htpasswdfile)
htpasswd.update(name, passwd)
htpasswd.save()
return True
return False
def getCurrentSoftwareReleaseProfile(config): def getCurrentSoftwareReleaseProfile(config):
""" """
Returns used Software Release profile as a string. Returns used Software Release profile as a string.
...@@ -231,7 +269,7 @@ def isSoftwareRunning(config=None): ...@@ -231,7 +269,7 @@ def isSoftwareRunning(config=None):
return isRunning('slapgrid-sr') return isRunning('slapgrid-sr')
def runSoftwareWithLock(config): def runSoftwareWithLock(config, lock=True):
""" """
Use Slapgrid to compile current Software Release and wait until Use Slapgrid to compile current Software Release and wait until
compilation is done compilation is done
...@@ -243,24 +281,21 @@ def runSoftwareWithLock(config): ...@@ -243,24 +281,21 @@ def runSoftwareWithLock(config):
if not os.path.exists(config['software_root']): if not os.path.exists(config['software_root']):
os.mkdir(config['software_root']) os.mkdir(config['software_root'])
stopProxy(config) stopProxy(config)
removeProxyDb(config)
startProxy(config) startProxy(config)
logfile = open(config['software_log'], 'w') logfile = open(config['software_log'], 'w')
if not updateProxy(config): if not updateProxy(config):
return False return False
# Accelerate compilation by setting make -jX
# XXX-Marco can have issues with implicit dependencies or recursive makefiles. should be configurable.
environment = os.environ.copy()
environment['MAKEFLAGS'] = '-j%r' % multiprocessing.cpu_count()
slapgrid = Popen([config['slapgrid_sr'], '-vc', slapgrid = Popen([config['slapgrid_sr'], '-vc',
'--pidfile', slapgrid_pid, '--pidfile', slapgrid_pid,
config['configuration_file_path'], '--now', '--develop'], config['configuration_file_path'], '--now', '--develop'],
stdout=logfile, env=environment, stdout=logfile, name='slapgrid-sr')
name='slapgrid-sr') if lock:
slapgrid.wait() slapgrid.wait()
#Saves the current compile software for re-use #Saves the current compile software for re-use
config_SR_folder(config) config_SR_folder(config)
return True return ( True if slapgrid.returncode == 0 else False )
else:
return False
def config_SR_folder(config): def config_SR_folder(config):
...@@ -276,12 +311,16 @@ def config_SR_folder(config): ...@@ -276,12 +311,16 @@ def config_SR_folder(config):
if len(cfg) != 2: if len(cfg) != 2:
continue # there is a broken config file continue # there is a broken config file
list.append(cfg[1]) 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: if not folder_list:
return return
current_project = open(os.path.join(config['etc_dir'], ".project")).read() current_project = open(os.path.join(config['etc_dir'], ".project")).read()
projects = current_project.split('/') if current_project[-1] == '/':
name = projects[-2] current_project = current_project[:-1]
name = current_project.split('/')[-1]
for folder in folder_list: for folder in folder_list:
if folder in list: if folder in list:
continue # this folder is already registered continue # this folder is already registered
...@@ -321,7 +360,7 @@ def isInstanceRunning(config=None): ...@@ -321,7 +360,7 @@ def isInstanceRunning(config=None):
return isRunning('slapgrid-cp') return isRunning('slapgrid-cp')
def runInstanceWithLock(config): def runInstanceWithLock(config, lock=True):
""" """
Use Slapgrid to deploy current Software Release and wait until Use Slapgrid to deploy current Software Release and wait until
deployment is done. deployment is done.
...@@ -337,9 +376,12 @@ def runInstanceWithLock(config): ...@@ -337,9 +376,12 @@ def runInstanceWithLock(config):
slapgrid = Popen([config['slapgrid_cp'], '-vc', slapgrid = Popen([config['slapgrid_cp'], '-vc',
'--pidfile', slapgrid_pid, '--pidfile', slapgrid_pid,
config['configuration_file_path'], '--now'], config['configuration_file_path'], '--now'],
stdout=logfile, name='slapgrid-cp') stdout=logfile, name='slapgrid-cp')
slapgrid.wait() if lock:
return True slapgrid.wait()
return ( True if slapgrid.returncode == 0 else False )
else:
return False
def getProfilePath(projectDir, profile): def getProfilePath(projectDir, profile):
...@@ -692,6 +734,7 @@ def realpath(config, path, check_exist=True): ...@@ -692,6 +734,7 @@ def realpath(config, path, check_exist=True):
'software_root': config['software_root'], 'software_root': config['software_root'],
'instance_root': config['instance_root'], 'instance_root': config['instance_root'],
'workspace': config['workspace'], 'workspace': config['workspace'],
'runner_workdir': config['runner_workdir'],
'software_link': config['software_link'] 'software_link': config['software_link']
} }
if key not in allow_list: if key not in allow_list:
...@@ -731,3 +774,101 @@ def readParameters(path): ...@@ -731,3 +774,101 @@ def readParameters(path):
return str(e) return str(e)
else: else:
return "No such file or directory: %s" % path 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')):
if config['autorun'] in TRUE_VALUES:
runSlapgridUntilSuccess(config, 'instance')
return "1"
else:
if isSoftwareRunning(config):
return "2"
elif config['auto_deploy'] in TRUE_VALUES:
configNewSR(config, path)
runSoftwareWithLock(config)
config_SR_folder(config)
time.sleep(15)
if config['autorun'] in TRUE_VALUES:
runSlapgridUntilSuccess(config, 'instance')
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 runSlapgridUntilSuccess(config, step):
"""Run slapgrid-sr or slapgrid-cp several times,
in the maximum of the constant MAX_RUN_~~~~"""
params = getBuildAndRunParams(config)
if step == "instance":
max_tries = (params['max_run_instance'] if params['run_instance'] else 0)
runSlapgridWithLock = runInstanceWithLock
elif step == "software":
max_tries = (params['max_run_software'] if params['run_software'] else 0)
runSlapgridWithLock = runSoftwareWithLock
else:
return -1
counter_file = os.path.join(config['runner_workdir'], '.turn-left')
open(counter_file, 'w+').write(str(max_tries))
counter = max_tries
slapgrid = True
# XXX-Nico runSoftwareWithLock can return 0 or False (0==False)
while counter > 0:
counter -= 1
slapgrid = runSlapgridWithLock(config)
if slapgrid:
break
times_left = int(open(counter_file).read()) - 1
if times_left > 0 :
open(counter_file, 'w+').write(str(times_left))
counter = times_left
else :
counter = 0
max_tries -= counter
# run instance only if we are deploying the software release,
# if it is defined so, and sr is correctly deployed
if step == "software" and params['run_instance'] and slapgrid:
return (max_tries, runSlapgridUntilSuccess(config, "instance"))
else:
return max_tries
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,))
...@@ -3,38 +3,40 @@ ...@@ -3,38 +3,40 @@
# pylint: disable-msg=W0311,C0301,C0103,C0111 # pylint: disable-msg=W0311,C0301,C0103,C0111
import json
import os import os
import shutil import shutil
import thread
import urllib import urllib
import json import json
from flaskext.auth import Auth, AuthUser, login_required, logout
from flask import (Flask, request, redirect, url_for, render_template, from flask import (Flask, request, redirect, url_for, render_template,
g, flash, jsonify, session, abort, send_file) g, flash, jsonify, session, abort, send_file)
from slapos.runner.process import killRunningProcess from slapos.runner.process import killRunningProcess
from slapos.runner.utils import (checkSoftwareFolder, configNewSR, getProfilePath, from slapos.runner.utils import (checkSoftwareFolder, configNewSR,
listFolder, getProjectTitle, getSession, createNewUser, getProfilePath,
listFolder, getBuildAndRunParams,
getProjectTitle, getRcode, getSession,
getSlapStatus, getSvcStatus, getSlapStatus, getSvcStatus,
getSvcTailProcess, isInstanceRunning, getSvcTailProcess, isInstanceRunning,
isSoftwareRunning, isText, isSoftwareRunning, isSoftwareReleaseReady, isText,
loadSoftwareRList, md5sum, newSoftware, loadSoftwareRList, md5sum, newSoftware,
readFileFrom, readParameters, realpath, readFileFrom, readParameters, realpath,
removeInstanceRoot, removeProxyDb, removeInstanceRoot, removeProxyDb,
removeSoftwareByName, runInstanceWithLock, removeSoftwareByName, runInstanceWithLock,
runSoftwareWithLock, saveSession, runSoftwareWithLock, runSlapgridUntilSuccess,
saveSession, saveBuildAndRunParams,
svcStartStopProcess, svcStopAll, tail, svcStartStopProcess, svcStopAll, tail,
updateInstanceParameter) updateInstanceParameter)
from slapos.runner.fileBrowser import FileBrowser from slapos.runner.fileBrowser import FileBrowser
from slapos.runner.gittools import (cloneRepo, gitStatus, switchBranch, from slapos.runner.gittools import (cloneRepo, gitStatus, switchBranch,
addBranch, getDiff, gitPush, gitPull) addBranch, getDiff, gitCommit, gitPush, gitPull)
app = Flask(__name__) app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024 app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024
auth = Auth(app, login_url_name='login')
auth.user_timeout = 0
file_request = FileBrowser(app.config) file_request = FileBrowser(app.config)
# Setup default flask (werkzeug) parser # Setup default flask (werkzeug) parser
...@@ -46,32 +48,28 @@ def login_redirect(*args, **kwargs): ...@@ -46,32 +48,28 @@ def login_redirect(*args, **kwargs):
return redirect(url_for('login')) return redirect(url_for('login'))
#Access Control: Only static files and login pages are allowed to guest
@app.before_request @app.before_request
def before_request(): def before_request():
if request.path.startswith('/static'): if request.path.startswith('/static') \
or request.path == '/isSRReady':
return return
account = getSession(app.config) account = getSession(app.config)
if account: if account:
user = AuthUser(username=account[0])
user.set_and_encrypt_password(account[1], "123400ZYX")
session['title'] = getProjectTitle(app.config) session['title'] = getProjectTitle(app.config)
g.users = {account[0]: user}
else: else:
session['title'] = "No account is defined" session['title'] = "No account is defined"
if request.path != "/setAccount" and request.path != "/configAccount": if request.path != "/setAccount" and request.path != "/configAccount":
return redirect(url_for('setAccount')) return redirect(url_for('setAccount'))
g.instance_monitoring_url = app.config['instance_monitoring_url']
# general views # general views
@login_required()
def home(): def home():
return render_template('index.html') return render_template('index.html')
# general views # general views
@login_required()
def browseWorkspace(): def browseWorkspace():
return render_template('workspace.html') return render_template('workspace.html')
...@@ -89,20 +87,13 @@ def setAccount(): ...@@ -89,20 +87,13 @@ def setAccount():
return redirect(url_for('login')) return redirect(url_for('login'))
@login_required()
def myAccount(): def myAccount():
account = getSession(app.config) account = getSession(app.config)
return render_template('account.html', username=account[0], return render_template('account.html', username=account[0],
email=account[2], name=account[3].decode('utf-8')) email=account[2], name=account[3].decode('utf-8'),
params=getBuildAndRunParams(app.config))
@app.route("/dologout")
def dologout():
_ = logout()
return redirect(url_for('login'))
@login_required()
def manageRepository(): def manageRepository():
public_key = open(app.config['public_key']).read() public_key = open(app.config['public_key']).read()
account = getSession(app.config) account = getSession(app.config)
...@@ -114,16 +105,12 @@ def manageRepository(): ...@@ -114,16 +105,12 @@ def manageRepository():
@app.route("/doLogin", methods=['POST']) @app.route("/doLogin", methods=['POST'])
def doLogin(): def doLogin():
username = request.form['clogin'] #XXX Now has to check the .htpasswd if we want to warn
if username in g.users: #the user that he misspelled his name/password
# Authenticate and log in! return jsonify(code=1, result="")
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
# software views # software views
@login_required()
def editSoftwareProfile(): def editSoftwareProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['software_profile']) profile = getProfilePath(app.config['etc_dir'], app.config['software_profile'])
if profile == "": if profile == "":
...@@ -132,14 +119,12 @@ def editSoftwareProfile(): ...@@ -132,14 +119,12 @@ def editSoftwareProfile():
profile=profile, projectList=listFolder(app.config, 'workspace')) profile=profile, projectList=listFolder(app.config, 'workspace'))
@login_required()
def inspectSoftware(): def inspectSoftware():
return render_template('runResult.html', softwareRoot='software_link/', return render_template('runResult.html', softwareRoot='software_link/',
softwares=loadSoftwareRList(app.config)) softwares=loadSoftwareRList(app.config))
#remove content of compiled software release #remove content of compiled software release
@login_required()
def removeSoftware(): def removeSoftware():
if isSoftwareRunning(app.config) or isInstanceRunning(app.config): if isSoftwareRunning(app.config) or isInstanceRunning(app.config):
flash('Software installation or instantiation in progress, cannot remove') flash('Software installation or instantiation in progress, cannot remove')
...@@ -152,16 +137,12 @@ def removeSoftware(): ...@@ -152,16 +137,12 @@ def removeSoftware():
return redirect(url_for('inspectSoftware')) return redirect(url_for('inspectSoftware'))
@login_required()
def runSoftwareProfile(): def runSoftwareProfile():
if runSoftwareWithLock(app.config): thread.start_new_thread(runSlapgridUntilSuccess, (app.config, "software"))
return jsonify(result=True) return jsonify(result=True)
else:
return jsonify(result=False)
# instance views # instance views
@login_required()
def editInstanceProfile(): def editInstanceProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['instance_profile']) profile = getProfilePath(app.config['etc_dir'], app.config['instance_profile'])
if profile == "": if profile == "":
...@@ -171,7 +152,6 @@ def editInstanceProfile(): ...@@ -171,7 +152,6 @@ def editInstanceProfile():
# get status of all computer partitions and process state # get status of all computer partitions and process state
@login_required()
def inspectInstance(): def inspectInstance():
if os.path.exists(app.config['instance_root']): if os.path.exists(app.config['instance_root']):
file_path = 'instance_root' file_path = 'instance_root'
...@@ -187,7 +167,6 @@ def inspectInstance(): ...@@ -187,7 +167,6 @@ def inspectInstance():
#Reload instance process ans returns new value to ajax #Reload instance process ans returns new value to ajax
@login_required()
def supervisordStatus(): def supervisordStatus():
result = getSvcStatus(app.config) result = getSvcStatus(app.config)
if not result: if not result:
...@@ -204,7 +183,6 @@ def supervisordStatus(): ...@@ -204,7 +183,6 @@ def supervisordStatus():
return jsonify(code=1, result=html) return jsonify(code=1, result=html)
@login_required()
def removeInstance(): def removeInstance():
if isInstanceRunning(app.config): if isInstanceRunning(app.config):
flash('Instantiation in progress, cannot remove') flash('Instantiation in progress, cannot remove')
...@@ -219,21 +197,17 @@ def removeInstance(): ...@@ -219,21 +197,17 @@ def removeInstance():
return redirect(url_for('inspectInstance')) return redirect(url_for('inspectInstance'))
@login_required()
def runInstanceProfile(): def runInstanceProfile():
if not os.path.exists(app.config['instance_root']): if not os.path.exists(app.config['instance_root']):
os.mkdir(app.config['instance_root']) os.mkdir(app.config['instance_root'])
if runInstanceWithLock(app.config): thread.start_new_thread(runSlapgridUntilSuccess, (app.config, "instance"))
return jsonify(result=True) return jsonify(result=True)
else:
return jsonify(result=False)
@login_required()
def viewLog(): def viewLog():
return render_template('viewLog.html') return render_template('viewLog.html')
@login_required()
def getFileLog(): def getFileLog():
logfile = request.form.get('filename', '').encode('utf-8') logfile = request.form.get('filename', '').encode('utf-8')
if logfile == "instance.log": if logfile == "instance.log":
...@@ -256,31 +230,26 @@ def getFileLog(): ...@@ -256,31 +230,26 @@ def getFileLog():
return jsonify(code=0, result="Warning: Log file doesn't exist yet or empty log!!") return jsonify(code=0, result="Warning: Log file doesn't exist yet or empty log!!")
@login_required()
def stopAllPartition(): def stopAllPartition():
svcStopAll(app.config) svcStopAll(app.config)
return redirect(url_for('inspectInstance')) return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def tailProcess(process): def tailProcess(process):
return render_template('processTail.html', return render_template('processTail.html',
process_log=getSvcTailProcess(app.config, process), process=process) process_log=getSvcTailProcess(app.config, process), process=process)
@login_required(login_redirect)
def startStopProccess(process, action): def startStopProccess(process, action):
svcStartStopProcess(app.config, process, action) svcStartStopProcess(app.config, process, action)
return redirect(url_for('inspectInstance')) return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def openProject(method): def openProject(method):
return render_template('projectFolder.html', method=method, return render_template('projectFolder.html', method=method,
workDir='workspace') workDir='workspace')
@login_required()
def cloneRepository(): def cloneRepository():
path = realpath(app.config, request.form['name'], False) path = realpath(app.config, request.form['name'], False)
data = { data = {
...@@ -292,23 +261,20 @@ def cloneRepository(): ...@@ -292,23 +261,20 @@ def cloneRepository():
return cloneRepo(data) return cloneRepo(data)
@login_required()
def listDirectory(): def listDirectory():
folderList = listFolder(app.config, request.form['name']) folderList = listFolder(app.config, request.form['name'])
return jsonify(result=folderList) return jsonify(result=folderList)
@login_required()
def createSoftware(): def createSoftware():
return newSoftware(request.form['folder'], app.config, session) return newSoftware(request.form['folder'], app.config, session)
@login_required()
def checkFolder(): def checkFolder():
return checkSoftwareFolder(request.form['path'], app.config) return checkSoftwareFolder(request.form['path'], app.config)
@login_required()
def setCurrentProject(): def setCurrentProject():
if configNewSR(app.config, request.form['path']): if configNewSR(app.config, request.form['path']):
session['title'] = getProjectTitle(app.config) session['title'] = getProjectTitle(app.config)
...@@ -317,7 +283,6 @@ def setCurrentProject(): ...@@ -317,7 +283,6 @@ def setCurrentProject():
return jsonify(code=0, result=("Can not setup this Software Release")) return jsonify(code=0, result=("Can not setup this Software Release"))
@login_required()
def getProjectStatus(): def getProjectStatus():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
...@@ -327,20 +292,21 @@ def getProjectStatus(): ...@@ -327,20 +292,21 @@ def getProjectStatus():
#view for current software release files #view for current software release files
@login_required()
def editCurrentProject(): def editCurrentProject():
project = os.path.join(app.config['etc_dir'], ".project") project = os.path.join(app.config['etc_dir'], ".project")
projectList = listFolder(app.config, 'workspace') projectList = listFolder(app.config, 'workspace')
if os.path.exists(project) and projectList: if os.path.exists(project) and projectList:
return render_template('softwareFolder.html', workDir='workspace', return render_template('softwareFolder.html', workDir='runner_workdir',
project=open(project).read(), project=open(project).read(),
projectList=projectList) projectList=projectList)
flash('Please clone slapos repository and then, <br/>open or create a software to start with your project!!') elif not projectList:
return redirect(url_for('manageRepository')) flash('Please clone slapos repository, or your own repository')
return redirect(url_for('manageRepository'))
else:
flash('Please, <br/>open or create a software to start with your project!!')
return redirect(url_for('openProject', method='open'))
#create file or directory #create file or directory
@login_required()
def createFile(): def createFile():
path = realpath(app.config, request.form['file'], False) path = realpath(app.config, request.form['file'], False)
if not path: if not path:
...@@ -356,7 +322,6 @@ def createFile(): ...@@ -356,7 +322,6 @@ def createFile():
#remove file or directory #remove file or directory
@login_required()
def removeFile(): def removeFile():
try: try:
if request.form['type'] == "folder": if request.form['type'] == "folder":
...@@ -368,7 +333,6 @@ def removeFile(): ...@@ -368,7 +333,6 @@ def removeFile():
return jsonify(code=0, result=str(e)) return jsonify(code=0, result=str(e))
@login_required()
def removeSoftwareDir(): def removeSoftwareDir():
try: try:
data = removeSoftwareByName(app.config, request.form['md5'], data = removeSoftwareByName(app.config, request.form['md5'],
...@@ -379,7 +343,6 @@ def removeSoftwareDir(): ...@@ -379,7 +343,6 @@ def removeSoftwareDir():
#read file and return content to ajax #read file and return content to ajax
@login_required()
def getFileContent(): def getFileContent():
file_path = realpath(app.config, request.form['file']) file_path = realpath(app.config, request.form['file'])
if file_path: if file_path:
...@@ -395,7 +358,6 @@ def getFileContent(): ...@@ -395,7 +358,6 @@ def getFileContent():
return jsonify(code=0, result="Error: No such file!") return jsonify(code=0, result="Error: No such file!")
@login_required()
def saveFileContent(): def saveFileContent():
file_path = realpath(app.config, request.form['file'], False) file_path = realpath(app.config, request.form['file'], False)
if file_path: if file_path:
...@@ -406,7 +368,6 @@ def saveFileContent(): ...@@ -406,7 +368,6 @@ def saveFileContent():
return jsonify(code=0, result="Rejected!! Cannot access to this location.") return jsonify(code=0, result="Rejected!! Cannot access to this location.")
@login_required()
def changeBranch(): def changeBranch():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
...@@ -415,7 +376,6 @@ def changeBranch(): ...@@ -415,7 +376,6 @@ def changeBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied") return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def newBranch(): def newBranch():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
...@@ -427,7 +387,6 @@ def newBranch(): ...@@ -427,7 +387,6 @@ def newBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied") return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required(login_redirect)
def getProjectDiff(): def getProjectDiff():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
...@@ -437,16 +396,14 @@ def getProjectDiff(): ...@@ -437,16 +396,14 @@ def getProjectDiff():
result="Error: No such file or directory. PERMISSION DENIED!") result="Error: No such file or directory. PERMISSION DENIED!")
@login_required()
def pushProjectFiles(): def pushProjectFiles():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
return gitPush(path, request.form['msg']) return gitPush(path)
else: else:
return jsonify(code=0, result="Can not read folder: Permission Denied") return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def pullProjectFiles(): def pullProjectFiles():
path = realpath(app.config, request.form['project']) path = realpath(app.config, request.form['project'])
if path: if path:
...@@ -455,7 +412,6 @@ def pullProjectFiles(): ...@@ -455,7 +412,6 @@ def pullProjectFiles():
return jsonify(code=0, result="Can not read folder: Permission Denied") return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def checkFileType(): def checkFileType():
path = realpath(app.config, request.form['path']) path = realpath(app.config, request.form['path'])
if not path: if not path:
...@@ -467,7 +423,6 @@ def checkFileType(): ...@@ -467,7 +423,6 @@ def checkFileType():
result="Can not open a binary file, please select a text file!") result="Can not open a binary file, please select a text file!")
@login_required()
def getmd5sum(): def getmd5sum():
realfile = realpath(app.config, request.form['file']) realfile = realpath(app.config, request.form['file'])
if not realfile: if not realfile:
...@@ -480,7 +435,6 @@ def getmd5sum(): ...@@ -480,7 +435,6 @@ def getmd5sum():
#return information about state of slapgrid process #return information about state of slapgrid process
@login_required()
def slapgridResult(): def slapgridResult():
software_state = isSoftwareRunning(app.config) software_state = isSoftwareRunning(app.config)
instance_state = isInstanceRunning(app.config) instance_state = isInstanceRunning(app.config)
...@@ -498,13 +452,13 @@ def slapgridResult(): ...@@ -498,13 +452,13 @@ def slapgridResult():
result=(instance_state or software_state), content=log_result) result=(instance_state or software_state), content=log_result)
@login_required()
def stopSlapgrid(): def stopSlapgrid():
counter_file = os.path.join(app.config['runner_workdir'], '.turn-left')
open(counter_file, 'w+').write(str(0))
result = killRunningProcess(request.form['type']) result = killRunningProcess(request.form['type'])
return jsonify(result=result) return jsonify(result=result)
@login_required()
def getPath(): def getPath():
files = request.form['file'].split('#') files = request.form['file'].split('#')
list = [] list = []
...@@ -523,7 +477,6 @@ def getPath(): ...@@ -523,7 +477,6 @@ def getPath():
return jsonify(code=1, result=realfile) return jsonify(code=1, result=realfile)
@login_required()
def saveParameterXml(): def saveParameterXml():
""" """
Update instance parameter into a local xml file. Update instance parameter into a local xml file.
...@@ -554,7 +507,6 @@ def saveParameterXml(): ...@@ -554,7 +507,6 @@ def saveParameterXml():
return jsonify(code=1, result="") return jsonify(code=1, result="")
@login_required()
def getSoftwareType(): def getSoftwareType():
software_type_path = os.path.join(app.config['etc_dir'], ".software_type.xml") software_type_path = os.path.join(app.config['etc_dir'], ".software_type.xml")
if os.path.exists(software_type_path): if os.path.exists(software_type_path):
...@@ -563,7 +515,6 @@ def getSoftwareType(): ...@@ -563,7 +515,6 @@ def getSoftwareType():
#read instance parameters into the local xml file and return a dict #read instance parameters into the local xml file and return a dict
@login_required()
def getParameterXml(request): def getParameterXml(request):
param_path = os.path.join(app.config['etc_dir'], ".parameter.xml") param_path = os.path.join(app.config['etc_dir'], ".parameter.xml")
if not os.path.exists(param_path): if not os.path.exists(param_path):
...@@ -579,11 +530,32 @@ def getParameterXml(request): ...@@ -579,11 +530,32 @@ def getParameterXml(request):
return jsonify(code=1, result=parameters) return jsonify(code=1, result=parameters)
#update user-defined slapgrid parameters
def updateBuildAndRunConfig():
code = 1
try:
max_run_instance = int(request.form['max_run_instance'].strip())
max_run_software = int(request.form['max_run_software'].strip())
except ValueError:
code = 0
result = "Error! You should have provided an integer"
run_instance = (True if request.form['run_instance']=="true" else False)
run_software = (True if request.form['run_software']=="true" else False)
if code:
params = {}
params['run_instance'] = run_instance
params['run_software'] = run_software
params['max_run_instance'] = max_run_instance
params['max_run_software'] = max_run_software
saveBuildAndRunParams(app.config, params)
result = "Your parameters have correctly been updated"
return jsonify(code=code, result=result)
#update user account data #update user account data
@login_required()
def updateAccount(): def updateAccount():
code = request.form['rcode'].strip() code = request.form['rcode'].strip()
recovery_code = open(os.path.join(app.config['etc_dir'], ".rcode"), "r").read() recovery_code = getRcode(app.config)
if code != recovery_code: if code != recovery_code:
return jsonify(code=0, result="Your password recovery code is not valid!") return jsonify(code=0, result="Your password recovery code is not valid!")
...@@ -614,8 +586,7 @@ def configAccount(): ...@@ -614,8 +586,7 @@ def configAccount():
account.append(request.form['email'].strip()) account.append(request.form['email'].strip())
account.append(request.form['name'].strip()) account.append(request.form['name'].strip())
code = request.form['rcode'].strip() code = request.form['rcode'].strip()
recovery_code = open(os.path.join(app.config['etc_dir'], ".rcode"), recovery_code = getRcode(app.config)
"r").read()
if code != recovery_code: if code != recovery_code:
return jsonify(code=0, result="Your password recovery code is not valid!") return jsonify(code=0, result="Your password recovery code is not valid!")
result = saveSession(app.config, account) result = saveSession(app.config, account)
...@@ -624,9 +595,19 @@ def configAccount(): ...@@ -624,9 +595,19 @@ def configAccount():
else: else:
return jsonify(code=1, result="") return jsonify(code=1, result="")
def addUser():
code = request.form['rcode'].strip()
recovery_code = getRcode(app.config)
if code != recovery_code:
return jsonify(code=0, result="Your password recovery code is not valid!")
if createNewUser(app.config, request.form['username'],
request.form['password']):
return jsonify(code=1, result="New user succesfully saved")
else:
return jsonify(code=0, result="Problem while creating new user")
#Global File Manager #Global File Manager
@login_required()
def fileBrowser(): def fileBrowser():
if request.method == 'POST': if request.method == 'POST':
filename = request.form.get('filename', '').encode('utf-8') filename = request.form.get('filename', '').encode('utf-8')
...@@ -698,13 +679,19 @@ def fileBrowser(): ...@@ -698,13 +679,19 @@ def fileBrowser():
return result return result
@login_required()
def editFile(): def editFile():
return render_template('editFile.html', workDir='workspace', return render_template('editFile.html', workDir='workspace',
profile=urllib.unquote(request.args.get('profile', '')), profile=urllib.unquote(request.args.get('profile', '')),
projectList=listFolder(app.config, 'workspace'), projectList=listFolder(app.config, 'workspace'),
filename=urllib.unquote(request.args.get('filename', ''))) filename=urllib.unquote(request.args.get('filename', '')))
def shell():
return render_template('shell.html')
def isSRReady():
return isSoftwareReleaseReady(app.config)
#Setup List of URLs #Setup List of URLs
app.add_url_rule('/', 'home', home) app.add_url_rule('/', 'home', home)
...@@ -780,6 +767,11 @@ app.add_url_rule("/getPath", 'getPath', getPath, methods=['POST']) ...@@ -780,6 +767,11 @@ app.add_url_rule("/getPath", 'getPath', getPath, methods=['POST'])
app.add_url_rule("/myAccount", 'myAccount', myAccount) app.add_url_rule("/myAccount", 'myAccount', myAccount)
app.add_url_rule("/updateAccount", 'updateAccount', updateAccount, app.add_url_rule("/updateAccount", 'updateAccount', updateAccount,
methods=['POST']) methods=['POST'])
app.add_url_rule("/updateBuildAndRunConfig", 'updateBuildAndRunConfig', updateBuildAndRunConfig,
methods=['POST'])
app.add_url_rule("/fileBrowser", 'fileBrowser', fileBrowser, app.add_url_rule("/fileBrowser", 'fileBrowser', fileBrowser,
methods=['GET', 'POST']) methods=['GET', 'POST'])
app.add_url_rule("/editFile", 'editFile', editFile, methods=['GET']) 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'])
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