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)
-------------------
......
......@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
import glob
import os
version = '0.37.1'
version = '0.39'
name = 'slapos.toolbox'
long_description = open("README.txt").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
......@@ -70,6 +70,7 @@ setup(name=name,
'onetimeupload = slapos.onetimeupload:main',
'pubsubnotifier = slapos.pubsub.notifier:main',
'pubsubserver = slapos.pubsub:main',
'qemu-qmp-client = slapos.qemuqmpclient:main',
'shacache = slapos.shacache:main',
'slapbuilder = slapos.builder:main',
'slapcontainer = slapos.container:main',
......
from datetime import datetime
import argparse
import csv
import feedparser
import httplib # To avoid magic numbers
import io
import socket
import json
import time
import math
import httplib # To avoid magic numbers
import argparse
import os
import socket
import sys
import time
from datetime import datetime
from hashlib import sha512
from atomize import Entry
......@@ -19,6 +20,9 @@ from flask import abort
from flask import request
app = Flask(__name__)
# csv entries can be very large, increase limit.
csv.field_size_limit(sys.maxsize)
@app.route('/get/<feed>')
def get_feed(feed):
global app
......
......@@ -4,7 +4,6 @@
import argparse
import csv
import httplib
import os
import socket
import subprocess
import sys
......@@ -32,28 +31,23 @@ def main():
args = parser.parse_args()
with open(os.devnull) as devnull:
command = subprocess.Popen(args.executable[0],
stdin=subprocess.PIPE,
stdout=devnull,
stderr=subprocess.PIPE,
close_fds=True)
command.stdin.flush()
command.stdin.close()
command_failed = (command.wait() != 0)
command_stderr = command.stderr.read()
if command_failed:
content = ("<p>Failed with returncode <em>%d</em>.</p>"
"<p>Standard error output is :</p><pre>%s</pre>") % (
command.poll(),
command_stderr.replace('&', '&amp;')\
.replace('<', '&lt;')\
.replace('>', '&gt;'),
)
else:
content = "<p>Everything went well.</p>"
try:
content = subprocess.check_output(
args.executable[0],
stderr=subprocess.STDOUT
)
exit_code = 0
except subprocess.CalledProcessError as e:
content = e.output
exit_code = e.returncode
print content
content += ("\n<p>Failed with returncode <em>%d</em>.</p>"
"<p>Output is: </p><pre>%s</pre>" % (
exit_code,
content.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
))
with open(args.logfile[0], 'a') as file_:
cvsfile = csv.writer(file_)
......@@ -64,9 +58,8 @@ def main():
'slapos:%s' % uuid.uuid4(),
])
if command_failed:
sys.stderr.write('%s\n' % command_stderr)
sys.exit(1)
if exit_code != 0:
sys.exit(exit_code)
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).
One entry point, if the service has been deployed
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"
instance without special parameter, and will manually run the test.
The other entry point, "bin/runStandaloneTest", is supposed to be run manually from a simple "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
Note: the slapos nodes are currently deployed using slapos-in-partition.
Note: you have to manually kill -10 the erp5testnode process to start deployment of test because it doesn't know when SR installation is finished.
Note: you have to manually run slapos-node-software --all on the slapos nodes if you are developping the SR you are testing.
------------
STANDALONE TESTS
Here is an example on how to deploy standalone tests on the webrunner, which means without using erp5.
1/ Deploy a SlapRunner software instance using the type test.
2/ In slapos.org, you should tell on which server you want to deploy your instances. You can adapt to your case the parameter.xml above. For the first time, you can deploy all the instances on the same node, it will run the tests faster, and it will be easier to debug :
<?xml version='1.0' encoding='utf-8'?>
<instance>
<parameter id="_">{"cluster": {"-sla-0-computer_guid": "COMP-XXXX", "-sla-1-computer_guid": "COMP-XXXX", "-sla-2-computer_guid": "COMP-XXXX"}}</parameter>
</instance>
3/ Then go to the root instance folder : it is the one who has only "runStandaloneResiliencyTestSuite" in its bin folder.
4/ Run ./bin/runStandaloneResiliencyTestSuite and wait :) it would return "success" or "failure"
......@@ -109,7 +109,9 @@ def runTestSuite(server_url, key_file, cert_file,
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.
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
......@@ -119,7 +121,7 @@ def runTestSuite(server_url, key_file, cert_file,
storage = 'storage.txt'
@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"
@app.route("/get")
......@@ -128,13 +130,39 @@ def runTestSuite(server_url, key_file, cert_file,
@app.route("/set")
def set():
if os.path.exists(storage):
abort(503)
#if os.path.exists(storage):
# abort(503)
open(storage, 'w').write(request.args['key'])
return "OK"
if __name__ == "__main__":
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.initializeConnection(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)
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",
# clones are named "kvm1", "kvm2", ...
clone_count = int(total_instance_count) - 1
......@@ -158,6 +183,10 @@ def runTestSuite(server_url, key_file, cert_file,
# Test each clone
while current_clone <= clone_count:
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)
time.sleep(SLEEP_TIME)
......
......@@ -32,6 +32,7 @@ import slapos.slap
import logging
import time
import os
class ResiliencyTestSuite(object):
"""
......@@ -104,6 +105,14 @@ class ResiliencyTestSuite(object):
"""
raise NotImplementedError('Overload me, I am an abstract method.')
def deleteTimestamp():
"""
XXX-Nicolas delete .timestamp in test partition to force the full processing
by slapgrid, to force the good parameters to be passed to the instances of the tree
"""
home = os.getenv('HOME')
timestamp = os.path.join(home, '.timestamp')
os.remove(timestamp)
def _getPartitionParameterDict(self):
"""
......@@ -115,6 +124,7 @@ class ResiliencyTestSuite(object):
software_type='resilient',
partition_reference=self.root_instance_name
).getConnectionParameterDict()
self.deleteTimestamp()
def _returnNewInstanceParameter(self, parameter_key, old_parameter_value):
"""
......@@ -126,8 +136,8 @@ class ResiliencyTestSuite(object):
new_parameter_value = None
while not new_parameter_value or new_parameter_value == 'None' or new_parameter_value == old_parameter_value:
self.logger.info('Not ready yet. SlapOS says new parameter value is %s' % new_parameter_value)
time.sleep(60)
new_parameter_value = self._getPartitionParameterDict().get(parameter_key, None)
time.sleep(120)
self.logger.info('New parameter value of instance is %s' % new_parameter_value)
return new_parameter_value
......
......@@ -28,7 +28,10 @@
from .resiliencytestsuite import ResiliencyTestSuite
import base64
import cookielib
import json
from lxml import etree
import random
import string
import time
......@@ -63,6 +66,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
300
)
def _connectToSlaprunner(self, resource, data=None):
"""
Utility.
......@@ -81,41 +85,49 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
def _login(self):
self.logger.debug('Logging in...')
self._connectToSlaprunner('doLogin', data='clogin=%s&cpwd=%s' % (
self.slaprunner_user,
self.slaprunner_password)
)
b64string = base64.encodestring('%s:%s' % (self.slaprunner_user, self.slaprunner_password))[:-1]
self._opener_director.addheaders = [('Authorization', 'Basic %s'%b64string)]
def _retrieveInstanceLogFile(self):
"""
Store the logfile (=data) of the instance, check it is not empty nor it is
html.
"""
time.sleep(30)
data = self._connectToSlaprunner(
resource='fileBrowser',
data='opt=9&filename=log.log&dir=instance_root%252Fslappart0%252Fvar%252Flog%252F'
resource='getFileContent',
data="file=instance_root/slappart0/var/log/log.log"
)
self.logger.info('Retrieved data are:\n%s' % data)
if data.find('<') is not -1:
raise IOError(
'Could not retrieve logfile content: retrieved content is html.'
)
if data.find('Could not load') is not -1:
raise IOError(
'Could not retrieve logfile content: server could not load the file.'
)
if data.find('Hello') is -1:
raise IOError(
'Could not retrieve logfile content: retrieve content does not match "Hello".'
)
try:
data = json.loads(data)['result']
self.logger.info('Retrieved data are:\n%s' % data)
except (ValueError, KeyError):
if data.find('<') is not -1:
raise IOError(
'Could not retrieve logfile content: retrieved content is html.'
)
if data.find('Could not load') is not -1:
raise IOError(
'Could not retrieve logfile content: server could not load the file.'
)
if data.find('Hello') is -1:
raise IOError(
'Could not retrieve logfile content: retrieve content does not match "Hello".'
)
return data
def _waitForSoftwareBuild(self):
while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1:
self.logger.info('Software release is still building. Sleeping...')
time.sleep(15)
self.logger.info('Software Release has been built / is no longer building.')
#while self._connectToSlaprunner(resource='slapgridResult', data='position=0&log=').find('"software": true') is not -1:
# self.logger.info('Software release is still building. Sleeping...')
# time.sleep(15)
#self.logger.info('Software Release has been built / is no longer building.')
try:
while self._connectToSlaprunner(resource='isSRReady') != "1":
self.logger.info('Software release is still building. Sleeping...')
time.sleep(15)
except (NotHttpOkException, urllib2.HTTPError):
# The nginx frontend might timeout before software release is finished.
self._waitForSoftwareBuild()
def _buildSoftwareRelease(self):
self.logger.info('Building the Software Release...')
......@@ -158,6 +170,20 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
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):
self.slaprunner_password = ''.join(
......@@ -180,7 +206,8 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
parameter_dict = self._getPartitionParameterDict()
self.slaprunner_backend_url = parameter_dict['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._connectToSlaprunner(
......@@ -199,6 +226,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
self._openSoftwareRelease('helloworld')
self._buildSoftwareRelease()
time.sleep(15)
self._deployInstance()
self.data = self._retrieveInstanceLogFile()
......@@ -219,9 +247,7 @@ class SlaprunnerTestSuite(ResiliencyTestSuite):
)
self._login()
self._waitForSoftwareBuild()
# XXX: in theory, it should be done automatically by slaprunner.
# In practice, it is still too dangerous for ERP5 instances.
self._deployInstance()
time.sleep(15)
new_data = self._retrieveInstanceLogFile()
if new_data == self.data:
......
......@@ -6,52 +6,14 @@ import ConfigParser
import datetime
import logging
import logging.handlers
from optparse import OptionParser, Option
import os
import slapos.runner.process
from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys
from slapos.runner.utils import runInstanceWithLock
from slapos.runner.views import *
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]
TRUE_VALUES = (1, '1', True, 'true', 'True')
class Config:
def __init__(self):
......@@ -61,22 +23,18 @@ class Config:
self.logger = None
self.verbose = None
def setConfig(self, option_dict, configuration_file_path):
def setConfig(self):
"""
Set options given by parameters.
"""
self.configuration_file_path = os.path.abspath(configuration_file_path)
# Set options parameters
for option, value in option_dict.__dict__.items():
setattr(self, option, value)
self.configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
# Load configuration file
configuration_parser = ConfigParser.SafeConfigParser()
configuration_parser.read(configuration_file_path)
# Merges the arguments and configuration
configuration_parser.read(self.configuration_file_path)
for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient", "cloud9_IDE"):
"sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict:
if not getattr(self, key, None):
......@@ -88,17 +46,17 @@ class Config:
if self.console:
self.logger.addHandler(logging.StreamHandler())
if self.log_file:
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.log_file = self.log_dir + '/slaprunner.log'
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.logger.info("Started.")
self.logger.info(os.environ['PATH'])
......@@ -107,30 +65,46 @@ class Config:
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():
"Run default configuration."
usage = "usage: %s [options] CONFIGURATION_FILE" % sys.argv[0]
try:
# Parse arguments
config = Config()
config.setConfig(*Parser(usage=usage).check_args())
# Parse arguments
config = Config()
config.setConfig()
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
return_code = 0
except SystemExit as err:
# Catch exception raise by optparse
return_code = err
sys.exit(return_code)
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
def serve(config):
from views import app
from werkzeug.contrib.fixers import ProxyFix
workdir = os.path.join(config.runner_workdir, 'project')
software_link = os.path.join(config.runner_workdir, 'softwareLink')
......@@ -145,14 +119,17 @@ def serve(config):
SECRET_KEY=os.urandom(24),
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
)
checkHtpasswd(app.config)
checkJSONConfig(app.config)
if not os.path.exists(workdir):
os.mkdir(workdir)
if not os.path.exists(software_link):
os.mkdir(software_link)
slapos.runner.process.setHandler()
setHandler()
config.logger.info('Running slapgrid...')
runInstanceWithLock(app.config)
if app.config['auto_deploy_instance'] in TRUE_VALUES:
runInstanceWithLock(app.config)
config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app)
app.run(host=config.runner_host, port=int(config.runner_port),
debug=config.debug, threaded=True)
run()
......@@ -121,36 +121,40 @@ def getDiff(project):
result = safeResult(str(e))
return result
def gitCommit(project, msg):
"""Commit changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
repo = Repo(project)
if repo.is_dirty:
git = repo.git
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
else:
code = 1
json = "Nothing to be commited"
return jsonify(code=code, result=json)
def gitPush(project, msg):
"""Commit and Push changes for the specified repository
def gitPush(project):
"""Push changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
undo_commit = False
try:
repo = Repo(project)
if repo.is_dirty:
git = repo.git
current_branch = repo.active_branch.name
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
undo_commit = True
#push changes to repo
git.push('origin', current_branch)
code = 1
else:
json = "Nothing to commit"
code = 1
#push changes to repo
current_branch = repo.active_branch.name
git.push('origin', current_branch)
code = 1
except Exception as e:
if undo_commit:
git.reset("HEAD~") # undo previous commit
json = safeResult(str(e))
return jsonify(code=code, result=json)
......
......@@ -2,6 +2,14 @@
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904
#############################################
# !!! Attention !!!
# You now have to comment the last line
# in __init__py, wich starts the functiun
# run() in order to start the tests,
# or it will NOT work
#############################################
import argparse
import ConfigParser
import datetime
......@@ -12,11 +20,16 @@ import shutil
import time
import unittest
from slapos.runner.utils import (getProfilePath, getSession, isInstanceRunning,
isSoftwareRunning, startProxy)
from slapos.runner.utils import (getProfilePath, getRcode,
getSession, isInstanceRunning,
isSoftwareRunning, startProxy,
isSoftwareReleaseReady,
runSlapgridUntilSuccess,
getBuildAndRunParams, saveBuildAndRunParams)
from slapos.runner.process import killRunningProcess, isRunning
from slapos.runner import views
import slapos.slap
from slapos.htpasswd import HtpasswdFile
#Helpers
......@@ -43,7 +56,7 @@ class Config:
# Merges the arguments and configuration
for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient", "cloud9_IDE"):
"sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict:
if not getattr(self, key, None):
......@@ -57,13 +70,11 @@ class SlaprunnerTestCase(unittest.TestCase):
views.app.config['TESTING'] = True
self.users = ["slapuser", "slappwd", "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.software = "workspace/slapos/software/" # relative directory fo SR
self.project = 'slapos' # Default project name
self.template = 'template.cfg'
self.partitionPrefix = 'slappart'
self.slaposBuildout = "1.6.0-dev-SlapOS-010"
#create slaprunner configuration
config = Config()
config.setConfig()
......@@ -85,16 +96,29 @@ class SlaprunnerTestCase(unittest.TestCase):
software_profile='software.cfg',
SECRET_KEY="123456",
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
auto_deploy = True,
autorun = False,
instance_monitoring_url = 'https://[' + config.ipv6_address + ']:9684',
)
self.app = views.app.test_client()
self.app.config = views.app.config
#Create password recover code
with open(os.path.join(views.app.config['etc_dir'], '.rcode'), 'w') as rpwd:
rpwd.write(self.rcode)
parser = ConfigParser.ConfigParser()
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):
"""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')
users = os.path.join(self.app.config['etc_dir'], '.users')
......@@ -110,7 +134,6 @@ class SlaprunnerTestCase(unittest.TestCase):
shutil.rmtree(self.app.config['instance_root'])
if os.path.exists(self.app.config['software_link']):
shutil.rmtree(self.app.config['software_link'])
self.logout()
#Stop process
killRunningProcess('slapproxy', recursive=True)
killRunningProcess('slapgrid-cp', recursive=True)
......@@ -128,26 +151,11 @@ class SlaprunnerTestCase(unittest.TestCase):
),
follow_redirects=True)
def login(self, username, password):
"""Helper for Login method"""
return self.app.post('/doLogin',
data=dict(
clogin=username,
cpwd=password
),
follow_redirects=True)
def setAccount(self):
"""Initialize user account and log user in"""
response = loadJson(self.configAccount(self.users[0], self.users[1],
self.users[2], self.users[3], self.rcode))
response2 = loadJson(self.login(self.users[0], self.users[1]))
self.assertEqual(response['result'], "")
self.assertEqual(response2['result'], "")
def logout(self):
"""Helper for Logout current user"""
return self.app.get('/dologout', follow_redirects=True)
def updateAccount(self, newaccount, rcode):
"""Helper for update user account data"""
......@@ -186,8 +194,7 @@ class SlaprunnerTestCase(unittest.TestCase):
sr += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n"
sr += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n\n"
sr += "[versions]\nzc.buildout = %s\n" % self.slaposBuildout
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n zc.buildout\n\n"
os.mkdir(testSoftware)
open(os.path.join(testSoftware, self.app.config['software_profile']),
'w').write(sr)
......@@ -216,13 +223,6 @@ class SlaprunnerTestCase(unittest.TestCase):
"""Kill slapproxy process"""
killRunningProcess('slapproxy', recursive=True)
#Begin test case here
def test_wrong_login(self):
"""Test Login user before create session. This should return an error value"""
response = self.login(self.users[0], self.users[1])
#redirect to config account page
assert "<h2 class='title'>Your personal information</h2><br/>" in response.data
def test_configAccount(self):
"""For the first lauch of slaprunner user need do create first account"""
result = self.configAccount(self.users[0], self.users[1], self.users[2],
......@@ -232,34 +232,16 @@ class SlaprunnerTestCase(unittest.TestCase):
account = getSession(self.app.config)
self.assertEqual(account, self.users)
def test_login_logout(self):
"""test login with good and wrong values, test logout"""
response = loadJson(self.configAccount(self.users[0], self.users[1],
self.users[2], self.users[3], self.rcode))
self.assertEqual(response['result'], "")
result = loadJson(self.login(self.users[0], "wrongpwd"))
self.assertEqual(result['result'], "Login or password is incorrect, please check it!")
resultwr = loadJson(self.login("wronglogin", "wrongpwd"))
self.assertEqual(resultwr['result'], "Login or password is incorrect, please check it!")
#try now with true values
resultlg = loadJson(self.login(self.users[0], self.users[1]))
self.assertEqual(resultlg['result'], "")
#after login test logout
result = self.logout()
assert "<h2>Login to Slapos Web Runner</h2>" in result.data
def test_updateAccount(self):
"""test Update accound, this needs the user to log in"""
self.setAccount()
htpasswd = os.path.join(self.app.config['etc_dir'], '.htpasswd')
assert self.users[0] in open(htpasswd).read()
response = loadJson(self.updateAccount(self.updateUser, self.rcode))
self.assertEqual(response['code'], 1)
result = self.logout()
assert "<h2>Login to Slapos Web Runner</h2>" in result.data
#retry login with new values
response = loadJson(self.login(self.updateUser[0], self.updateUser[1]))
self.assertEqual(response['result'], "")
#log out now!
self.logout()
encode = HtpasswdFile(htpasswd, False)
encode.update(self.updateUser[0], self.updateUser[1])
assert self.updateUser[0] in open(htpasswd).read()
def test_startProxy(self):
"""Test slapproxy"""
......@@ -301,7 +283,6 @@ class SlaprunnerTestCase(unittest.TestCase):
),
follow_redirects=True))
self.assertEqual(response['result'], "")
self.logout()
def test_createSR(self):
"""Scenario 2: Create a new software release"""
......@@ -315,13 +296,10 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertEqual(response['result'], "")
currentSR = self.getCurrentSR()
assert newSoftware in currentSR
self.logout()
def test_openSR(self):
"""Scenario 3: Open software release"""
self.test_cloneProject()
#Login
self.login(self.users[0], self.users[1])
software = os.path.join(self.software, 'drupal') # Drupal SR must exist in SR folder
response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software),
......@@ -336,7 +314,6 @@ class SlaprunnerTestCase(unittest.TestCase):
# newSoftware = os.path.join(self.software, 'slaprunner-test')
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_runSoftware(self):
"""Scenario 4: CReate empty SR and save software.cfg file
......@@ -345,8 +322,6 @@ class SlaprunnerTestCase(unittest.TestCase):
#Call config account
#call create software Release
self.test_createSR()
#Login
self.login(self.users[0], self.users[1])
newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
......@@ -362,10 +337,9 @@ class SlaprunnerTestCase(unittest.TestCase):
# Compile software and wait until slapgrid ends
# this is supposed to use current SR
response = loadJson(self.app.post('/runSoftwareProfile',
data=dict(),
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")
self.assertTrue(os.path.exists(self.app.config['software_root']))
self.assertTrue(os.path.exists(self.app.config['software_log']))
assert "test-application" in open(self.app.config['software_log']).read()
......@@ -376,7 +350,6 @@ class SlaprunnerTestCase(unittest.TestCase):
self.assertTrue(os.path.exists(createdFile))
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_updateInstanceParameter(self):
"""Scenarion 5: Update parameters of current sofware profile"""
......@@ -419,24 +392,25 @@ class SlaprunnerTestCase(unittest.TestCase):
response = loadJson(self.app.get('/getParameterXml/dict'))
self.assertEqual(parameterDict, response['result']['instance'])
self.stopSlapproxy()
self.logout()
def test_requestInstance(self):
"""Scenarion 6: request software instance"""
self.test_updateInstanceParameter()
#Login
self.login(self.users[0], self.users[1])
self.proxyStatus(False, sleep_time=1)
#run Software profile
response = loadJson(self.app.post('/runSoftwareProfile',
data=dict(),
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
response = loadJson(self.app.post('/runInstanceProfile',
data=dict(),
follow_redirects=True))
self.assertTrue(response['result'])
# lets some time to the Instance to be deployed
time.sleep(5)
#Check that all partitions has been created
assert "create-file" in open(self.app.config['instance_log']).read()
instanceDir = os.listdir(self.app.config['instance_root'])
......@@ -453,7 +427,96 @@ class SlaprunnerTestCase(unittest.TestCase):
assert 'simple file' in open(createdFile).read()
self.proxyStatus(True)
self.stopSlapproxy()
self.logout()
def test_safeAutoDeploy(self):
"""Scenario 7: isSRReady won't overwrite the existing
Sofware Instance if it has been deployed yet"""
# Test that SR won't be deployed with auto_deploy=False
self.app.config['auto_deploy'] = False
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test')
project.close()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "0")
# Test if auto_deploy parameter starts the deployment of SR
self.app.config['auto_deploy'] = True
self.setupSoftwareFolder()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "2")
# Test that the new call to isSoftwareReleaseReady
# doesn't overwrite the previous installed one
completed_path = os.path.join(self.app.config['runner_workdir'],
'softwareLink', 'slaprunner-test', '.completed')
completed_text = ".completed file: test"
completed = open(completed_path, "w")
completed.write(completed_text)
completed.close()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "1")
assert completed_text in open(completed_path).read()
def 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():
......
......@@ -48,7 +48,7 @@ th{
}
table.small th{padding: 4px;font-size: 16px;}
textarea {
width:932px;
width:932px;
font-family: 'Arial,Helvetica Neue',Tahoma,Helvetica,sans-serif;
}
......@@ -257,6 +257,10 @@ input[type="radio"], input[type="checkbox"]{
margin-bottom:10px;
}
#commitmsg {
width:95%;
}
.message {
color:#FF5500;
line-height:21px;
......@@ -406,7 +410,7 @@ padding: 10px;height: 80px;padding-bottom:15px;}
}
#code{
float: right;
width: 692px;
width: 680px;
}
#details_head{margin-bottom: 10px;}
......@@ -629,6 +633,26 @@ a.no-right-border:focus{border-right:none}
a.lshare img{
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 {
margin: 0;
......@@ -772,4 +796,24 @@ padding:10px; font-size:14px; color:#03406A}
.ace_search{
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 () {
return false;
}
send = true;
var base_url = 'https://' + $("input#username").val() + ':'
+ $("input#password").val() + '@' + location.host
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + ((hasAccount) ? '/updateAccount' : '/configAccount'),
......@@ -57,7 +59,8 @@ $(document).ready(function () {
},
success: function (data) {
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 {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
}
......@@ -67,4 +70,68 @@ $(document).ready(function () {
});
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() {
}
});
}(jQuery, document, this));
......@@ -27,7 +27,9 @@ $(document).ready(function () {
$("#login").removeClass("button").addClass("dsblebutton");
$.post(url, param, function (data) {
if (data.code === 1) {
window.location.href = $SCRIPT_ROOT + '/';
url = 'https://' + param.clogin + ':' + param.cpwd + '@'
+ location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else {
$("#error").Popup(data.result, {type: 'alert', duration: 3000});
}
......
......@@ -89,12 +89,6 @@ function getRunningState() {
}
}).error(function () {
clearAll(false);
}).complete(function () {
if (running) {
setTimeout(function () {
getRunningState();
}, speed);
}
});
}
......@@ -147,7 +141,8 @@ function bindRun() {
} else {
if (!isRunning()) {
setCookie("slapgridCMD", "Instance");
window.location.href = $SCRIPT_ROOT + "/viewLog";
if (window.location.pathname === "/viewLog")
window.location.href = $SCRIPT_ROOT + "/viewLog";
}
}
return false;
......@@ -173,6 +168,13 @@ function updateStatus(elt, val) {
$(src).children('p').text("Processing");
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) {
......@@ -197,6 +199,13 @@ function setRunningState(data) {
$("#softrun").addClass('slapos_stop');
$("#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";
}
}
......@@ -215,6 +224,7 @@ function setRunningState(data) {
$("#slapswitch").text('Access application');
}
$("#running").hide();
$("#running_info").remove();
running = false; //nothing is currently running
$("#softrun").removeClass('slapos_stop');
$("#softrun").addClass('slapos_run');
......@@ -238,11 +248,14 @@ function runProcess(urlfor, data) {
if ( $("#running_info").children('span').length > 0 ) {
$("#running_info").children('p').remove();
}
setRunningState(data);
setTimeout(getRunningState, 6000);
}
}
setInterval('GetStateRegularly()', 800);
function GetStateRegularly() {
getRunningState();
}
function checkSavedCmd() {
"use strict";
var result = getCookie("slapgridCMD");
......
......@@ -2,6 +2,13 @@
/*global $, document, $SCRIPT_ROOT */
/* vim: set et sts=4: */
$.valHooks.textarea = {
get: function (elem) {
"use strict";
return elem.value.replace(/\r?\n/g, "\r\n");
}
};
$(document).ready(function () {
"use strict";
......@@ -23,7 +30,7 @@ $(document).ready(function () {
var project = $("#project").val(),
urldata = $("input#workdir").val() + "/" + project;
$("#status").empty();
$("#push").hide();
$("#commit").hide();
$("#flash").empty();
if (project === "") {
$("#status").append("<h2>Please select one project...</h2><br/><br/>");
......@@ -48,7 +55,7 @@ $(document).ready(function () {
//alert(message);
$("#status").append("<p>" + message + "</p>");
if (data.dirty) {
$("#push").show();
$("#commit").show();
$("#status").append("<br/><h2>Display Diff for current Project</h2>");
$("#status").append("<p style='font-size:15px;'>You have changes in your project." +
" <a href='#' id='viewdiff'"
......@@ -162,9 +169,9 @@ $(document).ready(function () {
checkout("0");
return false;
});
$("#commit").click(function () {
$("#commitbutton").click(function () {
if ($("input#commitmsg").val() === "" ||
$("input#commitmsg").val() === "Enter message...") {
$("textarea#commitmsg").val() === "Enter message...") {
$("#error").Popup("Please Enter the commit message", {type: 'alert', duration: 3000});
return false;
}
......@@ -174,12 +181,12 @@ $(document).ready(function () {
send = true;
var project = $("#project").val();
$("#imgwaitting").fadeIn('normal');
$("#commit").empty();
$("#commit").attr("value", "Wait...");
//$("#commit").empty();
$("#commitbbutton").attr("value", "Wait...");
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/pushProjectFiles',
data: {project: $("input#workdir").val() + "/" + project, msg: $("input#commitmsg").val()},
url: $SCRIPT_ROOT + '/commitProjectFiles',
data: {project: $("input#workdir").val() + "/" + project, msg: $("textarea#commitmsg").val()},
success: function (data) {
if (data.code === 1) {
if (data.result !== "") {
......@@ -187,19 +194,46 @@ $(document).ready(function () {
} else {
$("#error").Popup("Commit done!", {type: 'confirm', duration: 3000});
}
$("#commit").hide();
gitStatus();
} else {
$("#error").Popup(data.result, {type: 'error'});
}
$("#imgwaitting").hide();
$("#commit").empty();
$("#commit").attr("value", "Commit");
$("#commitmsg").empty();
$("#commitbutton").attr("value", "Commit");
send = false;
}
});
return false;
});
$("#push").click(function () {
if (send) {
return false;
}
send = true;
var project = $("#project").val();
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + '/pushProjectFiles',
data: {project: $("input#workdir").val() + "/" + project},
success: function (data) {
if (data.code === 1) {
if (data.result !== "") {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
} else {
$("#error").Popup("The local commits have correctly been saved on the server", {type: 'confirm', duration: 3000});
}
gitStatus();
} else {
$("#error").Popup(data.result, {type: 'error'});
}
$("#push").hide();
send = false;
}
});
return false;
});
/*
$("#pullbranch").click(function (){
if (send){
......
......@@ -15,7 +15,7 @@ $(document).ready(function () {
softwareDisplay = true,
projectDir = $("input#project").val(),
workdir = $("input#workdir").val(),
currentProject = workdir + "/" + projectDir.replace(workdir, "").split('/')[1],
currentProject = "workspace/" + projectDir.replace(workdir, "").split('/')[1],
send = false,
edit = false,
ajaxResult = false,
......@@ -31,6 +31,7 @@ $(document).ready(function () {
var MIN_TABITEM_WIDTH = 61; //The minimum size of tabItem
var MAX_TAB_NUMBER = 10; //The maximum number of tab that could be opened
function alertStatus (jqXHR) {
if (jqXHR.status == 404) {
$("#error").Popup("Requested page not found. [404]", {type: 'error'});
......@@ -264,15 +265,26 @@ $(document).ready(function () {
editorlist[activeToken].changed = true;
$(activeSpan).html("*" + $(activeSpan).html());
}
if (!beforeunload_warning_set) {
window.onbeforeunload = function() { return "You have unsaved changes"; };
beforeunload_warning_set = true;
}
});
editor.commands.addCommand({
name: 'myCommand',
name: 'SaveText',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
$("#save").click();
},
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 () {
......@@ -302,7 +314,7 @@ $(document).ready(function () {
function switchContent() {
if (!softwareDisplay) {
$("span.swith_btn").empty();
$("span.swith_btn").append("Workspace");
$("span.swith_btn").append("Working dir");
$('#fileTreeFull').show();
$('#fileTree').hide();
} else {
......@@ -814,7 +826,7 @@ $(document).ready(function () {
modelist = require("ace/ext/modelist");
config = require("ace/config");
initTree('#fileTree', currentProject, 'pfolder');
initTree('#fileTreeFull', 'workspace');
initTree('#fileTreeFull', 'runner_workdir');
//bindContextMenu('#fileTree');
$("#info").append("Current work tree: " + base_path());
......@@ -827,6 +839,8 @@ $(document).ready(function () {
if ($("#tabControl div.item").length === 0) {
return false;
}
beforeunload_warning_set = false;
window.onbeforeunload = function() { return; };
var hash = getActiveToken();
if (editorlist[hash].busy) {
return false;
......@@ -901,6 +915,17 @@ $(document).ready(function () {
$("#option").click();
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 () {
if ($("#tabControl div.item").length === 0) {
......@@ -924,4 +949,10 @@ $(document).ready(function () {
return false;
});
$("#fullscreen").click(function(){
$("body").toggleClass("fullScreen");
$("#editor").toggleClass("fullScreen-editor");
editor.resize();
});
});
......@@ -6,8 +6,19 @@
$(document).ready(function () {
"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!!!
var current_log = 'instance.log',
var current_log = (getQueryVariable("logfile") !== undefined)? getQueryVariable("logfile") : "instance.log",
sending,
state,
selectedFile = "",
......
......@@ -11,7 +11,10 @@
<div id="tabContainer">
<ul>
<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 -->
<div class="tabDetails">
<div id="tab1" class="tabContents">
......@@ -33,7 +36,7 @@
<input type='password' name='cpassword' id='cpassword' value=''/>
<div class='clear'></div>
<br/>
<label for="rcode">Password Recover code:</label>
<label for="rcode">Password Recovery code:</label>
<input type='password' name='rcode' id='rcode' value=''/>
<span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span>
<div class='clear'></div>
......@@ -45,8 +48,52 @@
<input type="hidden" name="hasAccount" id="hasAccount" value="{{name}}"/>
</form>
</div>
<!--<div id="tab2" class="tabContents">
</div>-->
{% if params %}
<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>
{% if username %}<div id="file_info" class="file_info">leave passwords blank to preserve your current password...
......
......@@ -20,6 +20,7 @@
<li><a href="#tab2">Connection Information</a></li>
<li><a href="#tab3" id="parameterTab">Parameters</a></li>
<li><a href="#tab4" id="instancetabfiles">Partitions Content</a></li>
<li><a href="#tab5">Monitoring</a></li>
</ul><!-- //Tab buttons -->
<div class="tabDetails">
<div id="tab1" class="tabContents">
......@@ -132,6 +133,16 @@
</h2>
{%endif%}
</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>
<!-- This contains the hidden content for inline calls -->
......
......@@ -36,6 +36,7 @@
{% 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/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 type="text/javascript">
$(document).ready(function() {
......@@ -66,8 +67,6 @@
<div class="block_header">
<a href="{{ url_for('home') }}" style="float:left;" id="home" title="Home"><img alt="" src="{{ url_for('static', filename='images/home.png') }}" /></a>
<div class="line"></div>
<a href="{{ url_for('dologout') }}" style="float:left" title="Close your session"><img alt="" src="{{ url_for('static', filename='images/logout.png') }}" /></a>
<div class="line"></div>
<h2 class="info">{% block title %}{% endblock %} - {{session.title}}</h2>
<div class="run">
<div id="running" style="display:none">
......@@ -81,7 +80,7 @@
<li><a href="{{ url_for('editCurrentProject') }}">Editor</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') }}">Terminal</a></li>
<li><a href="{{ url_for('shell') }}">Terminal</a></li>
<li><a href="{{ url_for('manageRepository')}}#tab2">Git</a></li>
<li class='right_menu main_menu'><a href="#"></a>
<ul>
......@@ -94,9 +93,7 @@
<li><a href="{{ url_for('runInstanceProfile') }}" id="instrun">Redeploy Services</a></li>
<li class='sep'></li>
<li><a href="{{ url_for('browseWorkspace') }}">Browse Workspace</a></li>
<li><a href="{{ url_for('inspectSoftware') }}">My Softwares Releases</a></li>
<li class='sep'></li>
<li><a href="{{ url_for('dologout') }}">Log out</a></li>
<li><a href="{{ url_for('inspectSoftware') }}">My Software Releases</a></li>
</ul>
</li>
<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 @@
<!-- Definition of context menu -->
<ul id="fileTreeMenu" class="contextMenu">
<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="delete "><a href="#delete">Delete</a></li>
<li class="refresh separator"><a href="#refresh">Refresh</a></li>
......@@ -76,7 +76,7 @@
<ul class="inline">
<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="#">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='replace' href="#">Replace in file &nbsp;&nbsp;[Ctrl+H]</a></li>
</ul>
......
......@@ -37,7 +37,7 @@
<p>Processing</p>
<div class="clear"></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>
<h2 class="instance">Running State</h2>
......@@ -49,7 +49,7 @@
<p>Waiting for starting</p>
<div class="clear"></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 class="separator"></div>
......
......@@ -2,12 +2,15 @@
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,W0141,W0142
import ConfigParser
import json
import logging
import md5
import multiprocessing
import os
import re
import shutil
import os
import thread
import time
import urllib
from xml.dom import minidom
......@@ -15,6 +18,8 @@ from xml.dom import minidom
import xml_marshaller
from flask import jsonify
from slapos.runner.gittools import cloneRepo
from slapos.runner.process import Popen, isRunning, killRunningProcess
from slapos.htpasswd import HtpasswdFile
import slapos.slap
......@@ -23,6 +28,7 @@ import slapos.slap
logger = logging.getLogger('werkzeug')
TRUE_VALUES = (1, '1', True, 'true', 'True')
html_escape_table = {
"&": "&amp;",
......@@ -32,6 +38,19 @@ html_escape_table = {
"<": "&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):
"""Produce entities within text."""
......@@ -94,6 +113,25 @@ def saveSession(config, account):
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):
"""
Returns used Software Release profile as a string.
......@@ -231,7 +269,7 @@ def isSoftwareRunning(config=None):
return isRunning('slapgrid-sr')
def runSoftwareWithLock(config):
def runSoftwareWithLock(config, lock=True):
"""
Use Slapgrid to compile current Software Release and wait until
compilation is done
......@@ -243,24 +281,21 @@ def runSoftwareWithLock(config):
if not os.path.exists(config['software_root']):
os.mkdir(config['software_root'])
stopProxy(config)
removeProxyDb(config)
startProxy(config)
logfile = open(config['software_log'], 'w')
if not updateProxy(config):
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',
'--pidfile', slapgrid_pid,
config['configuration_file_path'], '--now', '--develop'],
stdout=logfile, env=environment,
name='slapgrid-sr')
slapgrid.wait()
#Saves the current compile software for re-use
config_SR_folder(config)
return True
stdout=logfile, name='slapgrid-sr')
if lock:
slapgrid.wait()
#Saves the current compile software for re-use
config_SR_folder(config)
return ( True if slapgrid.returncode == 0 else False )
else:
return False
def config_SR_folder(config):
......@@ -276,12 +311,16 @@ def config_SR_folder(config):
if len(cfg) != 2:
continue # there is a broken config file
list.append(cfg[1])
folder_list = os.listdir(config['software_root'])
if os.path.exists(config['software_root']):
folder_list = os.listdir(config['software_root'])
else:
return
if not folder_list:
return
current_project = open(os.path.join(config['etc_dir'], ".project")).read()
projects = current_project.split('/')
name = projects[-2]
if current_project[-1] == '/':
current_project = current_project[:-1]
name = current_project.split('/')[-1]
for folder in folder_list:
if folder in list:
continue # this folder is already registered
......@@ -321,7 +360,7 @@ def isInstanceRunning(config=None):
return isRunning('slapgrid-cp')
def runInstanceWithLock(config):
def runInstanceWithLock(config, lock=True):
"""
Use Slapgrid to deploy current Software Release and wait until
deployment is done.
......@@ -337,9 +376,12 @@ def runInstanceWithLock(config):
slapgrid = Popen([config['slapgrid_cp'], '-vc',
'--pidfile', slapgrid_pid,
config['configuration_file_path'], '--now'],
stdout=logfile, name='slapgrid-cp')
slapgrid.wait()
return True
stdout=logfile, name='slapgrid-cp')
if lock:
slapgrid.wait()
return ( True if slapgrid.returncode == 0 else False )
else:
return False
def getProfilePath(projectDir, profile):
......@@ -692,6 +734,7 @@ def realpath(config, path, check_exist=True):
'software_root': config['software_root'],
'instance_root': config['instance_root'],
'workspace': config['workspace'],
'runner_workdir': config['runner_workdir'],
'software_link': config['software_link']
}
if key not in allow_list:
......@@ -731,3 +774,101 @@ def readParameters(path):
return str(e)
else:
return "No such file or directory: %s" % path
def isSoftwareReleaseReady(config):
"""Return 1 if the Software Release has
correctly been deployed, 0 if not,
and 2 if it is currently deploying"""
project = os.path.join(config['etc_dir'], '.project')
if not os.path.exists(project):
return "0"
path = open(project, 'r').readline().strip()
software_name = path
if software_name[-1] == '/':
software_name = software_name[:-1]
software_name = software_name.split('/')[-1]
config_SR_folder(config)
if os.path.exists(os.path.join(config['runner_workdir'],
'softwareLink', software_name, '.completed')):
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 @@
# pylint: disable-msg=W0311,C0301,C0103,C0111
import json
import os
import shutil
import thread
import urllib
import json
from flaskext.auth import Auth, AuthUser, login_required, logout
from flask import (Flask, request, redirect, url_for, render_template,
g, flash, jsonify, session, abort, send_file)
from slapos.runner.process import killRunningProcess
from slapos.runner.utils import (checkSoftwareFolder, configNewSR, getProfilePath,
listFolder, getProjectTitle, getSession,
from slapos.runner.utils import (checkSoftwareFolder, configNewSR,
createNewUser, getProfilePath,
listFolder, getBuildAndRunParams,
getProjectTitle, getRcode, getSession,
getSlapStatus, getSvcStatus,
getSvcTailProcess, isInstanceRunning,
isSoftwareRunning, isText,
isSoftwareRunning, isSoftwareReleaseReady, isText,
loadSoftwareRList, md5sum, newSoftware,
readFileFrom, readParameters, realpath,
removeInstanceRoot, removeProxyDb,
removeSoftwareByName, runInstanceWithLock,
runSoftwareWithLock, saveSession,
runSoftwareWithLock, runSlapgridUntilSuccess,
saveSession, saveBuildAndRunParams,
svcStartStopProcess, svcStopAll, tail,
updateInstanceParameter)
from slapos.runner.fileBrowser import FileBrowser
from slapos.runner.gittools import (cloneRepo, gitStatus, switchBranch,
addBranch, getDiff, gitPush, gitPull)
addBranch, getDiff, gitCommit, gitPush, gitPull)
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024
auth = Auth(app, login_url_name='login')
auth.user_timeout = 0
file_request = FileBrowser(app.config)
# Setup default flask (werkzeug) parser
......@@ -46,32 +48,28 @@ def login_redirect(*args, **kwargs):
return redirect(url_for('login'))
#Access Control: Only static files and login pages are allowed to guest
@app.before_request
def before_request():
if request.path.startswith('/static'):
if request.path.startswith('/static') \
or request.path == '/isSRReady':
return
account = getSession(app.config)
if account:
user = AuthUser(username=account[0])
user.set_and_encrypt_password(account[1], "123400ZYX")
session['title'] = getProjectTitle(app.config)
g.users = {account[0]: user}
else:
session['title'] = "No account is defined"
if request.path != "/setAccount" and request.path != "/configAccount":
return redirect(url_for('setAccount'))
g.instance_monitoring_url = app.config['instance_monitoring_url']
# general views
@login_required()
def home():
return render_template('index.html')
# general views
@login_required()
def browseWorkspace():
return render_template('workspace.html')
......@@ -89,20 +87,13 @@ def setAccount():
return redirect(url_for('login'))
@login_required()
def myAccount():
account = getSession(app.config)
return render_template('account.html', username=account[0],
email=account[2], name=account[3].decode('utf-8'))
@app.route("/dologout")
def dologout():
_ = logout()
return redirect(url_for('login'))
email=account[2], name=account[3].decode('utf-8'),
params=getBuildAndRunParams(app.config))
@login_required()
def manageRepository():
public_key = open(app.config['public_key']).read()
account = getSession(app.config)
......@@ -114,16 +105,12 @@ def manageRepository():
@app.route("/doLogin", methods=['POST'])
def doLogin():
username = request.form['clogin']
if username in g.users:
# Authenticate and log in!
if g.users[username].authenticate(request.form['cpwd']):
return jsonify(code=1, result="")
return jsonify(code=0, result="Login or password is incorrect, please check it!"), 401
#XXX Now has to check the .htpasswd if we want to warn
#the user that he misspelled his name/password
return jsonify(code=1, result="")
# software views
@login_required()
def editSoftwareProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['software_profile'])
if profile == "":
......@@ -132,14 +119,12 @@ def editSoftwareProfile():
profile=profile, projectList=listFolder(app.config, 'workspace'))
@login_required()
def inspectSoftware():
return render_template('runResult.html', softwareRoot='software_link/',
softwares=loadSoftwareRList(app.config))
#remove content of compiled software release
@login_required()
def removeSoftware():
if isSoftwareRunning(app.config) or isInstanceRunning(app.config):
flash('Software installation or instantiation in progress, cannot remove')
......@@ -152,16 +137,12 @@ def removeSoftware():
return redirect(url_for('inspectSoftware'))
@login_required()
def runSoftwareProfile():
if runSoftwareWithLock(app.config):
return jsonify(result=True)
else:
return jsonify(result=False)
thread.start_new_thread(runSlapgridUntilSuccess, (app.config, "software"))
return jsonify(result=True)
# instance views
@login_required()
def editInstanceProfile():
profile = getProfilePath(app.config['etc_dir'], app.config['instance_profile'])
if profile == "":
......@@ -171,7 +152,6 @@ def editInstanceProfile():
# get status of all computer partitions and process state
@login_required()
def inspectInstance():
if os.path.exists(app.config['instance_root']):
file_path = 'instance_root'
......@@ -187,7 +167,6 @@ def inspectInstance():
#Reload instance process ans returns new value to ajax
@login_required()
def supervisordStatus():
result = getSvcStatus(app.config)
if not result:
......@@ -204,7 +183,6 @@ def supervisordStatus():
return jsonify(code=1, result=html)
@login_required()
def removeInstance():
if isInstanceRunning(app.config):
flash('Instantiation in progress, cannot remove')
......@@ -219,21 +197,17 @@ def removeInstance():
return redirect(url_for('inspectInstance'))
@login_required()
def runInstanceProfile():
if not os.path.exists(app.config['instance_root']):
os.mkdir(app.config['instance_root'])
if runInstanceWithLock(app.config):
return jsonify(result=True)
else:
return jsonify(result=False)
thread.start_new_thread(runSlapgridUntilSuccess, (app.config, "instance"))
return jsonify(result=True)
@login_required()
def viewLog():
return render_template('viewLog.html')
@login_required()
def getFileLog():
logfile = request.form.get('filename', '').encode('utf-8')
if logfile == "instance.log":
......@@ -256,31 +230,26 @@ def getFileLog():
return jsonify(code=0, result="Warning: Log file doesn't exist yet or empty log!!")
@login_required()
def stopAllPartition():
svcStopAll(app.config)
return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def tailProcess(process):
return render_template('processTail.html',
process_log=getSvcTailProcess(app.config, process), process=process)
@login_required(login_redirect)
def startStopProccess(process, action):
svcStartStopProcess(app.config, process, action)
return redirect(url_for('inspectInstance'))
@login_required(login_redirect)
def openProject(method):
return render_template('projectFolder.html', method=method,
workDir='workspace')
@login_required()
def cloneRepository():
path = realpath(app.config, request.form['name'], False)
data = {
......@@ -292,23 +261,20 @@ def cloneRepository():
return cloneRepo(data)
@login_required()
def listDirectory():
folderList = listFolder(app.config, request.form['name'])
return jsonify(result=folderList)
@login_required()
def createSoftware():
return newSoftware(request.form['folder'], app.config, session)
@login_required()
def checkFolder():
return checkSoftwareFolder(request.form['path'], app.config)
@login_required()
def setCurrentProject():
if configNewSR(app.config, request.form['path']):
session['title'] = getProjectTitle(app.config)
......@@ -317,7 +283,6 @@ def setCurrentProject():
return jsonify(code=0, result=("Can not setup this Software Release"))
@login_required()
def getProjectStatus():
path = realpath(app.config, request.form['project'])
if path:
......@@ -327,20 +292,21 @@ def getProjectStatus():
#view for current software release files
@login_required()
def editCurrentProject():
project = os.path.join(app.config['etc_dir'], ".project")
projectList = listFolder(app.config, 'workspace')
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(),
projectList=projectList)
flash('Please clone slapos repository and then, <br/>open or create a software to start with your project!!')
return redirect(url_for('manageRepository'))
elif not projectList:
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
@login_required()
def createFile():
path = realpath(app.config, request.form['file'], False)
if not path:
......@@ -356,7 +322,6 @@ def createFile():
#remove file or directory
@login_required()
def removeFile():
try:
if request.form['type'] == "folder":
......@@ -368,7 +333,6 @@ def removeFile():
return jsonify(code=0, result=str(e))
@login_required()
def removeSoftwareDir():
try:
data = removeSoftwareByName(app.config, request.form['md5'],
......@@ -379,7 +343,6 @@ def removeSoftwareDir():
#read file and return content to ajax
@login_required()
def getFileContent():
file_path = realpath(app.config, request.form['file'])
if file_path:
......@@ -395,7 +358,6 @@ def getFileContent():
return jsonify(code=0, result="Error: No such file!")
@login_required()
def saveFileContent():
file_path = realpath(app.config, request.form['file'], False)
if file_path:
......@@ -406,7 +368,6 @@ def saveFileContent():
return jsonify(code=0, result="Rejected!! Cannot access to this location.")
@login_required()
def changeBranch():
path = realpath(app.config, request.form['project'])
if path:
......@@ -415,7 +376,6 @@ def changeBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def newBranch():
path = realpath(app.config, request.form['project'])
if path:
......@@ -427,7 +387,6 @@ def newBranch():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required(login_redirect)
def getProjectDiff():
path = realpath(app.config, request.form['project'])
if path:
......@@ -437,16 +396,14 @@ def getProjectDiff():
result="Error: No such file or directory. PERMISSION DENIED!")
@login_required()
def pushProjectFiles():
path = realpath(app.config, request.form['project'])
if path:
return gitPush(path, request.form['msg'])
return gitPush(path)
else:
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def pullProjectFiles():
path = realpath(app.config, request.form['project'])
if path:
......@@ -455,7 +412,6 @@ def pullProjectFiles():
return jsonify(code=0, result="Can not read folder: Permission Denied")
@login_required()
def checkFileType():
path = realpath(app.config, request.form['path'])
if not path:
......@@ -467,7 +423,6 @@ def checkFileType():
result="Can not open a binary file, please select a text file!")
@login_required()
def getmd5sum():
realfile = realpath(app.config, request.form['file'])
if not realfile:
......@@ -480,7 +435,6 @@ def getmd5sum():
#return information about state of slapgrid process
@login_required()
def slapgridResult():
software_state = isSoftwareRunning(app.config)
instance_state = isInstanceRunning(app.config)
......@@ -498,13 +452,13 @@ def slapgridResult():
result=(instance_state or software_state), content=log_result)
@login_required()
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'])
return jsonify(result=result)
@login_required()
def getPath():
files = request.form['file'].split('#')
list = []
......@@ -523,7 +477,6 @@ def getPath():
return jsonify(code=1, result=realfile)
@login_required()
def saveParameterXml():
"""
Update instance parameter into a local xml file.
......@@ -554,7 +507,6 @@ def saveParameterXml():
return jsonify(code=1, result="")
@login_required()
def getSoftwareType():
software_type_path = os.path.join(app.config['etc_dir'], ".software_type.xml")
if os.path.exists(software_type_path):
......@@ -563,7 +515,6 @@ def getSoftwareType():
#read instance parameters into the local xml file and return a dict
@login_required()
def getParameterXml(request):
param_path = os.path.join(app.config['etc_dir'], ".parameter.xml")
if not os.path.exists(param_path):
......@@ -579,11 +530,32 @@ def getParameterXml(request):
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
@login_required()
def updateAccount():
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:
return jsonify(code=0, result="Your password recovery code is not valid!")
......@@ -614,8 +586,7 @@ def configAccount():
account.append(request.form['email'].strip())
account.append(request.form['name'].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:
return jsonify(code=0, result="Your password recovery code is not valid!")
result = saveSession(app.config, account)
......@@ -624,9 +595,19 @@ def configAccount():
else:
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
@login_required()
def fileBrowser():
if request.method == 'POST':
filename = request.form.get('filename', '').encode('utf-8')
......@@ -698,13 +679,19 @@ def fileBrowser():
return result
@login_required()
def editFile():
return render_template('editFile.html', workDir='workspace',
profile=urllib.unquote(request.args.get('profile', '')),
projectList=listFolder(app.config, 'workspace'),
filename=urllib.unquote(request.args.get('filename', '')))
def shell():
return render_template('shell.html')
def isSRReady():
return isSoftwareReleaseReady(app.config)
#Setup List of URLs
app.add_url_rule('/', 'home', home)
......@@ -780,6 +767,11 @@ app.add_url_rule("/getPath", 'getPath', getPath, methods=['POST'])
app.add_url_rule("/myAccount", 'myAccount', myAccount)
app.add_url_rule("/updateAccount", 'updateAccount', updateAccount,
methods=['POST'])
app.add_url_rule("/updateBuildAndRunConfig", 'updateBuildAndRunConfig', updateBuildAndRunConfig,
methods=['POST'])
app.add_url_rule("/fileBrowser", 'fileBrowser', fileBrowser,
methods=['GET', 'POST'])
app.add_url_rule("/editFile", 'editFile', editFile, methods=['GET'])
app.add_url_rule('/shell', 'shell', shell)
app.add_url_rule('/isSRReady', 'isSRReady', isSRReady)
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