279 lines
8.9 KiB
Python
279 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2015 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from collections import defaultdict
|
|
import os
|
|
from StringIO import StringIO
|
|
import yaml
|
|
|
|
from jinja2 import Template, Environment, meta
|
|
|
|
from solar.core import provider
|
|
from solar.core import signals
|
|
from solar.core.log import log
|
|
from solar.core.resource import load as load_resource
|
|
from solar.core.resource import Resource, load_by_tags
|
|
from solar.events.api import add_event
|
|
from solar.events.controls import React, Dep
|
|
|
|
|
|
def create(name, base_path, args=None, tags=None, virtual_resource=None):
|
|
args = args or {}
|
|
if isinstance(base_path, provider.BaseProvider):
|
|
base_path = base_path.directory
|
|
|
|
if not os.path.exists(base_path):
|
|
raise Exception(
|
|
'Base resource does not exist: {0}'.format(base_path)
|
|
)
|
|
|
|
if is_virtual(base_path):
|
|
template = _compile_file(name, base_path, args)
|
|
yaml_template = yaml.load(StringIO(template))
|
|
rs = create_virtual_resource(name, yaml_template, tags)
|
|
else:
|
|
r = create_resource(name,
|
|
base_path,
|
|
args=args,
|
|
tags=tags,
|
|
virtual_resource=virtual_resource)
|
|
rs = [r]
|
|
|
|
return rs
|
|
|
|
|
|
def create_resource(name, base_path, args=None, tags=None, virtual_resource=None):
|
|
args = args or {}
|
|
if isinstance(base_path, provider.BaseProvider):
|
|
base_path = base_path.directory
|
|
|
|
# List args init with empty list. Elements will be added later
|
|
def _filter(value):
|
|
if not isinstance(value, list):
|
|
return value
|
|
return filter(lambda res: not is_connection(res), value)
|
|
|
|
args = {key: _filter(value) for key, value in args.items()}
|
|
r = Resource(
|
|
name, base_path, args=args, tags=tags, virtual_resource=virtual_resource
|
|
)
|
|
return r
|
|
|
|
|
|
def create_virtual_resource(vr_name, template, tags=None):
|
|
template_resources = template.get('resources', [])
|
|
template_events = template.get('events', [])
|
|
resources_to_update = template.get('updates', [])
|
|
|
|
created_resources = create_resources(template_resources, tags=tags)
|
|
events = parse_events(template_events)
|
|
for event in events:
|
|
add_event(event)
|
|
update_resources(resources_to_update)
|
|
return created_resources
|
|
|
|
|
|
def _compile_file(name, path, kwargs):
|
|
with open(path) as f:
|
|
content = f.read()
|
|
|
|
inputs = get_inputs(content)
|
|
template = _get_template(name, content, kwargs, inputs)
|
|
with open('/tmp/compiled', 'w') as c:
|
|
c.write(template)
|
|
return template
|
|
|
|
|
|
def get_inputs(content):
|
|
env = Environment(trim_blocks=True, lstrip_blocks=True)
|
|
jinja_globals = env.globals.keys()
|
|
ast = env.parse(content)
|
|
return meta.find_undeclared_variables(ast) - set(jinja_globals)
|
|
|
|
|
|
def _get_template(name, content, kwargs, inputs):
|
|
missing = []
|
|
for input in inputs:
|
|
if input not in kwargs:
|
|
missing.append(input)
|
|
if missing:
|
|
raise Exception('[{0}] Validation error. Missing data in input: {1}'.format(name, missing))
|
|
template = Template(content, trim_blocks=True, lstrip_blocks=True)
|
|
template = template.render(str=str, zip=zip, **kwargs)
|
|
return template
|
|
|
|
|
|
def is_virtual(path):
|
|
return os.path.isfile(path)
|
|
|
|
|
|
def create_resources(resources, tags=None):
|
|
created_resources = []
|
|
cwd = os.getcwd()
|
|
for r in resources:
|
|
resource_name = r['id']
|
|
args = r.get('values', {})
|
|
node = r.get('location', None)
|
|
from_path = r.get('from', None)
|
|
tags = r.get('tags', [])
|
|
base_path = os.path.join(cwd, from_path)
|
|
new_resources = create(resource_name, base_path, args=args, tags=tags)
|
|
created_resources += new_resources
|
|
if not is_virtual(base_path):
|
|
if node:
|
|
node = load_resource(node)
|
|
r = new_resources[0]
|
|
node.connect(r, mapping={})
|
|
r.add_tags('location={}'.format(node.name))
|
|
update_inputs(resource_name, args)
|
|
return created_resources
|
|
|
|
|
|
def extend_resources(template_resources):
|
|
resources = []
|
|
for r in template_resources:
|
|
if r.get('id'):
|
|
resources.append(r)
|
|
if r.get('with_tags'):
|
|
tags = r.get('with_tags')
|
|
filtered = load_by_tags(tags)
|
|
for f in filtered:
|
|
r = {'id': f.name,
|
|
'values': r['values']}
|
|
resources.append(r)
|
|
log.debug('Resource {} for tags {} found'.format(r, tags))
|
|
if not filtered:
|
|
log.debug('Warrning: no resources with tags: {}'.format(tags))
|
|
return resources
|
|
|
|
def update_resources(template_resources):
|
|
resources = extend_resources(template_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)
|
|
parents = defaultdict(lambda: defaultdict(dict))
|
|
for c in connections:
|
|
mapping = {c['parent_input']: c['child_input']}
|
|
parents[c['parent']]['mapping'].update(mapping)
|
|
if parents[c['parent']].get('events', None) is None:
|
|
parents[c['parent']]['events'] = c['events']
|
|
|
|
for parent, data in parents.iteritems():
|
|
parent = load_resource(parent)
|
|
use_defaults = not data['events'] is False
|
|
mapping = data['mapping']
|
|
parent.connect_with_events(
|
|
child, mapping, {}, use_defaults=use_defaults)
|
|
|
|
child.update(assignments)
|
|
|
|
|
|
def extend_events(template_events):
|
|
events = []
|
|
for e in template_events:
|
|
if e.get('parent_action', None):
|
|
events.append(e)
|
|
elif e.get('parent', None):
|
|
parent = e.get('parent')
|
|
tags = parent.get('with_tags')
|
|
resources = load_by_tags(tags)
|
|
for r in resources:
|
|
parent_action = '{}.{}'.format(r.name, parent['action'])
|
|
event = {'type' : e['type'],
|
|
'state': e['state'],
|
|
'depend_action': e['depend_action'],
|
|
'parent_action': parent_action
|
|
}
|
|
events.append(event)
|
|
return events
|
|
|
|
def parse_events(template_events):
|
|
parsed_events = []
|
|
events = extend_events(template_events)
|
|
for event in events:
|
|
event_type = event['type']
|
|
parent, parent_action = event['parent_action'].split('.')
|
|
child, child_action = event['depend_action'].split('.')
|
|
state = event['state']
|
|
if event_type == Dep.etype:
|
|
event = Dep(parent, parent_action, state, child, child_action)
|
|
elif event_type == React.etype:
|
|
event = React(parent, parent_action, state, child, child_action)
|
|
else:
|
|
raise Exception('Invalid event type: {0}'.format(event_type))
|
|
parsed_events.append(event)
|
|
return parsed_events
|
|
|
|
|
|
|
|
|
|
def parse_inputs(args):
|
|
connections = []
|
|
assignments = {}
|
|
for r_input, arg in args.items():
|
|
if isinstance(arg, list):
|
|
c, a = parse_list_input(r_input, arg)
|
|
connections.extend(c)
|
|
assignments.update(a)
|
|
else:
|
|
if is_connection(arg):
|
|
c = parse_connection(r_input, arg)
|
|
connections.append(c)
|
|
else:
|
|
assignments[r_input] = arg
|
|
return connections, assignments
|
|
|
|
|
|
def parse_list_input(r_input, args):
|
|
connections = []
|
|
assignments = {}
|
|
for arg in args:
|
|
if is_connection(arg):
|
|
c = parse_connection(r_input, arg)
|
|
connections.append(c)
|
|
else:
|
|
try:
|
|
assignments[r_input].append(arg)
|
|
except KeyError:
|
|
assignments[r_input] = [arg]
|
|
return connections, assignments
|
|
|
|
|
|
def is_connection(arg):
|
|
if isinstance(arg, basestring) and '::' in arg:
|
|
return True
|
|
return False
|
|
|
|
|
|
def parse_connection(child_input, element):
|
|
parent, parent_input = element.split('::', 1)
|
|
try:
|
|
parent_input, events = parent_input.split('::')
|
|
if events == 'NO_EVENTS':
|
|
events = False
|
|
except ValueError:
|
|
events = None
|
|
return {'child_input': child_input,
|
|
'parent' : parent,
|
|
'parent_input': parent_input,
|
|
'events' : events
|
|
}
|