Merge pull request #198 from loles/vr_update

Add updates section
This commit is contained in:
Jędrzej Nowak 2015-09-25 09:40:19 +02:00
commit c041b5974f
3 changed files with 104 additions and 50 deletions

View File

@ -20,8 +20,9 @@ import yaml
from jinja2 import Template, Environment, meta from jinja2 import Template, Environment, meta
from solar.core import provider from solar.core import provider
from solar.core import resource
from solar.core import signals from solar.core import signals
from solar.core.resource import load as load_resource
from solar.core.resource import Resource
from solar.events.api import add_event from solar.events.api import add_event
from solar.events.controls import React, Dep from solar.events.controls import React, Dep
@ -57,20 +58,22 @@ def create_resource(name, base_path, args=None, virtual_resource=None):
# List args init with empty list. Elements will be added later # List args init with empty list. Elements will be added later
args = {key: (value if not isinstance(value, list) else []) for key, value in args.items()} args = {key: (value if not isinstance(value, list) else []) for key, value in args.items()}
r = resource.Resource( r = Resource(
name, base_path, args=args, tags=[], virtual_resource=virtual_resource name, base_path, args=args, tags=[], virtual_resource=virtual_resource
) )
return r return r
def create_virtual_resource(vr_name, template): def create_virtual_resource(vr_name, template):
template_resources = template['resources'] template_resources = template.get('resources', [])
template_events = template.get('events', {}) template_events = template.get('events', [])
resources_to_update = template.get('updates', [])
created_resources = create_resources(template_resources) created_resources = create_resources(template_resources)
events = parse_events(template_events) events = parse_events(template_events)
for event in events: for event in events:
add_event(event) add_event(event)
update_resources(resources_to_update)
return created_resources return created_resources
@ -113,16 +116,36 @@ def create_resources(resources):
cwd = os.getcwd() cwd = os.getcwd()
for r in resources: for r in resources:
resource_name = r['id'] resource_name = r['id']
base_path = os.path.join(cwd, r['from'])
args = r['values'] args = r['values']
new_resources = create(resource_name, base_path, args) from_path = r.get('from', None)
base_path = os.path.join(cwd, from_path)
new_resources = create(resource_name, base_path)
created_resources += new_resources created_resources += new_resources
if not is_virtual(base_path): if not is_virtual(base_path):
add_connections(resource_name, args) update_inputs(resource_name, args)
return created_resources return created_resources
def update_resources(resources):
for r in resources:
resource_name = r['id']
args = r['values']
update_inputs(resource_name, args)
def update_inputs(child, args):
child = load_resource(child)
connections, assignments = parse_inputs(args)
for c in connections:
mapping = {}
parent = load_resource(c['parent'])
events = c['events']
mapping[c['parent_input']] = c['child_input']
signals.connect(parent, child, mapping, events)
child.update(assignments)
def parse_events(events): def parse_events(events):
parsed_events = [] parsed_events = []
for event in events: for event in events:
@ -140,38 +163,46 @@ def parse_events(events):
return parsed_events return parsed_events
def add_connections(resource_name, args): def parse_inputs(args):
connections = [] connections = []
for receiver_input, arg in args.items(): assignments = {}
for r_input, arg in args.items():
if isinstance(arg, list): if isinstance(arg, list):
for item in arg: c, a = parse_list_input(r_input, arg)
c = parse_connection(resource_name, receiver_input, item) connections.extend(c)
assignments.update(a)
else:
if isinstance(arg, basestring) and '::' in arg:
c = parse_connection(r_input, arg)
connections.append(c) connections.append(c)
else: else:
c = parse_connection(resource_name, receiver_input, arg) assignments[r_input] = arg
return connections, assignments
def parse_list_input(r_input, args):
connections = []
assignments = {}
for arg in args:
if isinstance(arg, basestring) and '::' in arg:
c = parse_connection(r_input, arg)
connections.append(c) connections.append(c)
else:
connections = [c for c in connections if c is not None] # Not supported yet
for c in connections: raise Exception('Only connections are supported in lists')
parent = resource.load(c['parent']) return connections, assignments
child = resource.load(c['child'])
events = c['events']
mapping = {c['parent_input'] : c['child_input']}
signals.connect(parent, child, mapping, events)
def parse_connection(receiver, receiver_input, element): def parse_connection(child_input, element):
if isinstance(element, basestring) and '::' in element: parent, parent_input = element.split('::', 1)
emitter, src = element.split('::', 1)
try: try:
src, events = src.split('::') parent_input, events = parent_input.split('::')
if events == 'NO_EVENTS': if events == 'NO_EVENTS':
events = False events = False
except ValueError: except ValueError:
events = None events = None
return {'child': receiver, return {'child_input': child_input,
'child_input': receiver_input, 'parent' : parent,
'parent' : emitter, 'parent_input': parent_input,
'parent_input': src,
'events' : events 'events' : events
} }

View File

@ -0,0 +1,5 @@
id: simple_multinode
updates:
- id: node1
values:
ip: '10.0.0.4'

View File

@ -46,6 +46,11 @@ def bad_event_type():
''' '''
return yaml.load(StringIO(events)) return yaml.load(StringIO(events))
def test_create_path_does_not_exists():
with pytest.raises(Exception) as excinfo:
vr.create('node1', '/path/does/not/exists')
err = 'Base resource does not exist: /path/does/not/exists'
assert str(excinfo.value) == err
def test_create_resource(): def test_create_resource():
node_path = os.path.join( node_path = os.path.join(
@ -68,6 +73,26 @@ def test_create_virtual_resource(tmpdir):
resources = vr.create('nodes', str(vr_file)) resources = vr.create('nodes', str(vr_file))
assert len(resources) == 2 assert len(resources) == 2
def test_update(tmpdir):
# XXX: make helper for it
base_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'resource_fixtures')
vr_node_tmpl_path = os.path.join(base_path, 'nodes.yaml.tmpl')
vr_update_tmpl_path = os.path.join(base_path, 'update.yaml.tmpl')
update_path = os.path.join(base_path, 'update')
node_resource_path = os.path.join(base_path, 'node')
with open(vr_node_tmpl_path) as f:
vr_data = f.read().format(resource_path=node_resource_path)
with open(vr_update_tmpl_path) as f:
update_data = f.read().format(resource_path=update_path)
vr_file = tmpdir.join('nodes.yaml')
vr_file.write(vr_data)
update_file = tmpdir.join('update.yaml')
update_file.write(update_data)
resources = vr.create('nodes', str(vr_file))
vr.create('updates', str(update_file))
assert resources[0].args['ip'] == '10.0.0.4'
def test_parse_events(good_events): def test_parse_events(good_events):
events =[Dep(parent='service1', parent_action='run', events =[Dep(parent='service1', parent_action='run',
@ -92,30 +117,23 @@ def test_add_connections(mocker, resources):
'servers': ['node1::ip', 'node2::ip'], 'servers': ['node1::ip', 'node2::ip'],
'alias': 'ser1' 'alias': 'ser1'
} }
vr.add_connections('service1', args) vr.update_inputs('service1', args)
assert mocked_signals.connect.call_count == 3 assert mocked_signals.connect.call_count == 3
def test_parse_connection(): def test_parse_connection():
correct_connection = {'child': 'host_file', correct_connection = {'child_input': 'ip',
'child_input': 'ip',
'parent' : 'node1', 'parent' : 'node1',
'parent_input': 'ip', 'parent_input': 'ip',
'events' : None 'events' : None
} }
connection = vr.parse_connection('host_file', 'ip', 'node1::ip') connection = vr.parse_connection('ip', 'node1::ip')
assert correct_connection == connection assert correct_connection == connection
def test_parse_connection_disable_events(): def test_parse_connection_disable_events():
correct_connection = {'child': 'host_file', correct_connection = {'child_input': 'ip',
'child_input': 'ip',
'parent' : 'node1', 'parent' : 'node1',
'parent_input': 'ip', 'parent_input': 'ip',
'events' : False 'events' : False
} }
connection = vr.parse_connection('host_file', 'ip', 'node1::ip::NO_EVENTS') connection = vr.parse_connection('ip', 'node1::ip::NO_EVENTS')
assert correct_connection == connection assert correct_connection == connection
def test_parse_connection_no_connection():
connection = vr.parse_connection('host_file', 'ip', '10.0.0.2')
assert None == connection