Commit 00c3e8a5 authored by Łukasz Nowak's avatar Łukasz Nowak

Merge branch 'master' into networkcache_signature

Conflicts:
	slapos/tests/slapgrid.py
parents b175699c 1eddcede
......@@ -3,6 +3,7 @@ develop = .
parts =
slapos
pyflakes
test
find-links =
http://www.nexedi.org/static/packages/source/slapos.buildout/
......@@ -44,5 +45,10 @@ eggs =
rstctl
interpreter = python
[test]
recipe = zc.recipe.testrunner
eggs =
slapos.core
[versions]
zc.buildout = 1.5.3-dev-SlapOS-005
......@@ -29,6 +29,13 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5.Document.Item import Item
from lxml import etree
import collections
class DisconnectedSoftwareTree(Exception):
pass
class CyclicSoftwareTree(Exception):
pass
class SoftwareInstance(Item):
"""
......@@ -60,3 +67,44 @@ class SoftwareInstance(Item):
value = element.text
result_dict[key] = value
return result_dict
security.declareProtected(Permissions.AccessContentsInformation,
'checkNotCyclic')
def checkNotCyclic(self, graph):
# see http://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html
todo = set(graph.keys())
while todo:
node = todo.pop()
stack = [node]
while stack:
top = stack[-1]
for node in graph[top]:
if node in stack:
raise CyclicSoftwareTree
if node in todo:
stack.append(node)
todo.remove(node)
break
else:
node = stack.pop()
return True
security.declareProtected(Permissions.AccessContentsInformation,
'checkConnected')
def checkConnected(self, graph, root):
size = len(graph)
visited = set()
to_crawl = collections.deque(graph[root])
while to_crawl:
current = to_crawl.popleft()
if current in visited:
continue
visited.add(current)
node_children = set(graph[current])
to_crawl.extend(node_children - visited)
# add one to visited, as root won't be visited, only children
# this is false positive in case of cyclic graphs, but they are
# anyway wrong in Software Instance trees
if size != len(visited) + 1:
raise DisconnectedSoftwareTree
return True
......@@ -64,9 +64,18 @@ instance_xml = kwargs["instance_xml"]\n
sla_xml = kwargs["sla_xml"]\n
state = kwargs["state"]\n
\n
# Get root software instance\n
# graph allows to "simulate" tree change after requested operation\n
graph = {}\n
# Get root software instance and create initial graph\n
predecessor_software_instance = software_instance\n
while (predecessor_software_instance is not None):\n
predecessor_software_instance_pred_uid_list = predecessor_software_instance.getPredecessorUidList()\n
graph[predecessor_software_instance.getUid()] = predecessor_software_instance_pred_uid_list\n
# as this walking is not fetching all instances fill predecessors into graph, in order to have\n
# "nearly" complete representation\n
for uid in predecessor_software_instance_pred_uid_list:\n
if uid not in graph:\n
graph[uid] = []\n
root_software_instance = predecessor_software_instance\n
predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n
portal_type="Software Instance")\n
......@@ -84,6 +93,11 @@ request_software_instance = software_instance.portal_catalog.getResultValue(\n
root_uid=root_software_instance.getUid(),\n
)\n
\n
# above query does not find root software instance, but as this case is easy\n
# to find, just check if such request does not come and raise\n
if root_software_instance.getTitle() == requested_partition_reference:\n
raise ValueError(\'It is disallowed to request root software instance\')\n
\n
if (request_software_instance is None):\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n
......@@ -130,6 +144,8 @@ else:\n
predecessor_software_instance.edit(\n
predecessor_uid_list=predecessor_uid_list,\n
activate_kw={\'tag\': tag},)\n
graph[predecessor_software_instance.getUid()] = predecessor_uid_list\n
\n
if state == \'started\':\n
request_software_instance.startRequested()\n
request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n
......@@ -139,7 +155,16 @@ else:\n
else:\n
raise ValueError(\'State %r is not supported\' % state)\n
predecessor_list = software_instance.getPredecessorList() + [request_software_instance.getRelativeUrl()]\n
# Add requested software instance to graph if does not exists there yet\n
if not request_software_instance.getUid() in graph:\n
graph[request_software_instance.getUid()] = []\n
\n
# update graph to reflect requested operation\n
graph[software_instance.getUid()] = software_instance.getPredecessorUidList() + [request_software_instance.getUid()]\n
\n
# check if all elements are still connected and if there is no cycle\n
software_instance.checkConnected(graph, root_software_instance.getUid())\n
software_instance.checkNotCyclic(graph)\n
\n
software_instance.edit(\n
predecessor_list=predecessor_list,\n
......
246
\ No newline at end of file
254
\ No newline at end of file
......@@ -35,6 +35,7 @@ if sys.version_info < (2, 6):
import socket
import subprocess
import traceback
import time
#from time import strftime
from SlapObject import Software, Partition, WrongPermissionError, \
......@@ -106,6 +107,9 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple):
help="Enables console output and live output from subcommands.")
parser.add_argument("-v", "--verbose", action="store_true", default=False,
help="Be verbose.")
parser.add_argument("--promise-timeout",
type=int, default=3,
help="Promise timeout in seconds.")
parser.add_argument("configuration_file", nargs=1, type=argparse.FileType(),
help="SlapOS configuration file.")
......@@ -210,7 +214,8 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple):
upload_cache_url=option_dict.get('upload-cache-url', None),
upload_dir_url=option_dict.get('upload-dir-url', None),
console=option_dict['console'],
buildout=option_dict.get('buildout')),
buildout=option_dict.get('buildout'),
promise_timeout=option_dict['promise_timeout']),
option_dict])
......@@ -282,7 +287,8 @@ class Slapgrid(object):
upload_dir_url=None,
master_ca_file=None,
certificate_repository_path=None,
console=False):
console=False,
promise_timeout=3):
"""Makes easy initialisation of class parameters"""
# Parses arguments
self.software_root = os.path.abspath(software_root)
......@@ -312,6 +318,7 @@ class Slapgrid(object):
os.path.join(self.instance_etc_directory, 'supervisord.conf.d')
self.console = console
self.buildout = buildout
self.promise_timeout = promise_timeout
def checkEnvironmentAndCreateStructure(self):
"""Checks for software_root and instance_root existence, then creates
......@@ -473,6 +480,53 @@ class Slapgrid(object):
exception = traceback.format_exc()
logger.error(exception)
computer_partition.error(exception)
# Promises
instance_path = os.path.join(self.instance_root,
computer_partition.getId())
uid, gid = None, None
stat_info = os.stat(instance_path)
#stat sys call to get statistics informations
uid = stat_info.st_uid
gid = stat_info.st_gid
# Get the list of promises
promise_dir = os.path.join(instance_path, 'etc', 'promise')
if os.path.exists(promise_dir) and os.path.isdir(promise_dir):
cwd = instance_path
promises_list = os.listdir(promise_dir)
# Check whether every promise is kept
for promise in promises_list:
command = os.path.join(promise_dir, promise)
kw = dict()
if not self.console:
kw.update(stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process_handler = SlapPopen(command,
preexec_fn=lambda: dropPrivileges(uid, gid),
cwd=cwd,
env=None, **kw)
time.sleep(self.promise_timeout)
promise = os.path.basename(command)
if process_handler.poll() is None:
process_handler.kill()
computer_partition.error("The promise %r timed out" % promise)
elif process_handler.poll() != 0:
stderr = process_handler.communicate()[1]
if stderr is None:
stderr = 'No error output from %r.' % promise
computer_partition.error(stderr)
logger.info("Finished computer partitions...")
return clean_run
......
......@@ -3,8 +3,6 @@
"""Mocked httplib
"""
from urlparse import urlparse
__all__ = []
def log(message):
......@@ -21,13 +19,13 @@ class HTTPConnection(object):
HTTPConnection._callback. This method received the instance, the path,
method and request body as parameter, and it has to return a tuple with
headers dictionary and body response string.
@param self object instance reference
@param URL the parsed URL
@param method the http method
@param body the request body
@param headers the request headers
@return tuple containing status integer, headers dictionary and body
response"""
return (0, {}, '', )
......@@ -83,7 +81,6 @@ class HTTPSConnection(HTTPConnection):
source_address=None):
super().__init__(self, host, port, strict, timeout,
source_address)
pass
class HTTPResponse(object):
......
This diff is collapsed.
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