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 = . ...@@ -3,6 +3,7 @@ develop = .
parts = parts =
slapos slapos
pyflakes pyflakes
test
find-links = find-links =
http://www.nexedi.org/static/packages/source/slapos.buildout/ http://www.nexedi.org/static/packages/source/slapos.buildout/
...@@ -44,5 +45,10 @@ eggs = ...@@ -44,5 +45,10 @@ eggs =
rstctl rstctl
interpreter = python interpreter = python
[test]
recipe = zc.recipe.testrunner
eggs =
slapos.core
[versions] [versions]
zc.buildout = 1.5.3-dev-SlapOS-005 zc.buildout = 1.5.3-dev-SlapOS-005
...@@ -29,6 +29,13 @@ from AccessControl import ClassSecurityInfo ...@@ -29,6 +29,13 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5.Document.Item import Item from Products.ERP5.Document.Item import Item
from lxml import etree from lxml import etree
import collections
class DisconnectedSoftwareTree(Exception):
pass
class CyclicSoftwareTree(Exception):
pass
class SoftwareInstance(Item): class SoftwareInstance(Item):
""" """
...@@ -60,3 +67,44 @@ class SoftwareInstance(Item): ...@@ -60,3 +67,44 @@ class SoftwareInstance(Item):
value = element.text value = element.text
result_dict[key] = value result_dict[key] = value
return result_dict 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 ...@@ -64,9 +64,18 @@ instance_xml = kwargs["instance_xml"]\n
sla_xml = kwargs["sla_xml"]\n sla_xml = kwargs["sla_xml"]\n
state = kwargs["state"]\n state = kwargs["state"]\n
\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 predecessor_software_instance = software_instance\n
while (predecessor_software_instance is not None):\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 root_software_instance = predecessor_software_instance\n
predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n
portal_type="Software Instance")\n portal_type="Software Instance")\n
...@@ -84,6 +93,11 @@ request_software_instance = software_instance.portal_catalog.getResultValue(\n ...@@ -84,6 +93,11 @@ request_software_instance = software_instance.portal_catalog.getResultValue(\n
root_uid=root_software_instance.getUid(),\n root_uid=root_software_instance.getUid(),\n
)\n )\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 (request_software_instance is None):\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n # The software instance is already under creation but can not be fetched from catalog\n
...@@ -130,6 +144,8 @@ else:\n ...@@ -130,6 +144,8 @@ else:\n
predecessor_software_instance.edit(\n predecessor_software_instance.edit(\n
predecessor_uid_list=predecessor_uid_list,\n predecessor_uid_list=predecessor_uid_list,\n
activate_kw={\'tag\': tag},)\n activate_kw={\'tag\': tag},)\n
graph[predecessor_software_instance.getUid()] = predecessor_uid_list\n
\n
if state == \'started\':\n if state == \'started\':\n
request_software_instance.startRequested()\n request_software_instance.startRequested()\n
request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n
...@@ -139,7 +155,16 @@ else:\n ...@@ -139,7 +155,16 @@ else:\n
else:\n else:\n
raise ValueError(\'State %r is not supported\' % state)\n raise ValueError(\'State %r is not supported\' % state)\n
predecessor_list = software_instance.getPredecessorList() + [request_software_instance.getRelativeUrl()]\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 \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 \n
software_instance.edit(\n software_instance.edit(\n
predecessor_list=predecessor_list,\n predecessor_list=predecessor_list,\n
......
246 254
\ No newline at end of file \ No newline at end of file
...@@ -35,6 +35,7 @@ if sys.version_info < (2, 6): ...@@ -35,6 +35,7 @@ if sys.version_info < (2, 6):
import socket import socket
import subprocess import subprocess
import traceback import traceback
import time
#from time import strftime #from time import strftime
from SlapObject import Software, Partition, WrongPermissionError, \ from SlapObject import Software, Partition, WrongPermissionError, \
...@@ -106,6 +107,9 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple): ...@@ -106,6 +107,9 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple):
help="Enables console output and live output from subcommands.") help="Enables console output and live output from subcommands.")
parser.add_argument("-v", "--verbose", action="store_true", default=False, parser.add_argument("-v", "--verbose", action="store_true", default=False,
help="Be verbose.") 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(), parser.add_argument("configuration_file", nargs=1, type=argparse.FileType(),
help="SlapOS configuration file.") help="SlapOS configuration file.")
...@@ -210,7 +214,8 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple): ...@@ -210,7 +214,8 @@ def parseArgumentTupleAndReturnSlapgridObject(*argument_tuple):
upload_cache_url=option_dict.get('upload-cache-url', None), upload_cache_url=option_dict.get('upload-cache-url', None),
upload_dir_url=option_dict.get('upload-dir-url', None), upload_dir_url=option_dict.get('upload-dir-url', None),
console=option_dict['console'], console=option_dict['console'],
buildout=option_dict.get('buildout')), buildout=option_dict.get('buildout'),
promise_timeout=option_dict['promise_timeout']),
option_dict]) option_dict])
...@@ -282,7 +287,8 @@ class Slapgrid(object): ...@@ -282,7 +287,8 @@ class Slapgrid(object):
upload_dir_url=None, upload_dir_url=None,
master_ca_file=None, master_ca_file=None,
certificate_repository_path=None, certificate_repository_path=None,
console=False): console=False,
promise_timeout=3):
"""Makes easy initialisation of class parameters""" """Makes easy initialisation of class parameters"""
# Parses arguments # Parses arguments
self.software_root = os.path.abspath(software_root) self.software_root = os.path.abspath(software_root)
...@@ -312,6 +318,7 @@ class Slapgrid(object): ...@@ -312,6 +318,7 @@ class Slapgrid(object):
os.path.join(self.instance_etc_directory, 'supervisord.conf.d') os.path.join(self.instance_etc_directory, 'supervisord.conf.d')
self.console = console self.console = console
self.buildout = buildout self.buildout = buildout
self.promise_timeout = promise_timeout
def checkEnvironmentAndCreateStructure(self): def checkEnvironmentAndCreateStructure(self):
"""Checks for software_root and instance_root existence, then creates """Checks for software_root and instance_root existence, then creates
...@@ -473,6 +480,53 @@ class Slapgrid(object): ...@@ -473,6 +480,53 @@ class Slapgrid(object):
exception = traceback.format_exc() exception = traceback.format_exc()
logger.error(exception) logger.error(exception)
computer_partition.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...") logger.info("Finished computer partitions...")
return clean_run return clean_run
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
"""Mocked httplib """Mocked httplib
""" """
from urlparse import urlparse
__all__ = [] __all__ = []
def log(message): def log(message):
...@@ -21,13 +19,13 @@ class HTTPConnection(object): ...@@ -21,13 +19,13 @@ class HTTPConnection(object):
HTTPConnection._callback. This method received the instance, the path, HTTPConnection._callback. This method received the instance, the path,
method and request body as parameter, and it has to return a tuple with method and request body as parameter, and it has to return a tuple with
headers dictionary and body response string. headers dictionary and body response string.
@param self object instance reference @param self object instance reference
@param URL the parsed URL @param URL the parsed URL
@param method the http method @param method the http method
@param body the request body @param body the request body
@param headers the request headers @param headers the request headers
@return tuple containing status integer, headers dictionary and body @return tuple containing status integer, headers dictionary and body
response""" response"""
return (0, {}, '', ) return (0, {}, '', )
...@@ -83,7 +81,6 @@ class HTTPSConnection(HTTPConnection): ...@@ -83,7 +81,6 @@ class HTTPSConnection(HTTPConnection):
source_address=None): source_address=None):
super().__init__(self, host, port, strict, timeout, super().__init__(self, host, port, strict, timeout,
source_address) source_address)
pass
class HTTPResponse(object): 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