Merge pull request #52 from xarses/move-x-into-solar-core
Initial implementation of assign method
This commit is contained in:
commit
51674c00ad
@ -39,11 +39,9 @@ vim tmp/storage/nodes-id.yaml
|
||||
```
|
||||
|
||||
* add `env/test_env` in tags list
|
||||
* in order to assign resouce to the node use the same the same
|
||||
method, i.e. add in tags list for node your service e.g.
|
||||
`service/docker`, `service/mariadb`
|
||||
* perform deployment
|
||||
* assign resources to nodes
|
||||
|
||||
```
|
||||
solar configure --profile prf1 -pa run
|
||||
# TODO Does not work without default values in golden templates
|
||||
solar assign -n "env/test_env && node/1" -r resource/mariadb
|
||||
```
|
||||
|
@ -9,3 +9,5 @@ file-system-db:
|
||||
storage-path: /vagrant/tmp/storage
|
||||
|
||||
template-dir: /vagrant/templates
|
||||
resources-files-mask: /vagrant/x/resources/*/*.yaml
|
||||
resource-instances-path: /vagrant/tmp/resource-instances
|
||||
|
@ -3,4 +3,4 @@ pyyaml
|
||||
jinja2
|
||||
networkx
|
||||
mock
|
||||
|
||||
ply
|
||||
|
@ -11,6 +11,7 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Solar CLI api
|
||||
|
||||
On create "golden" resource should be moved to special place
|
||||
@ -27,6 +28,8 @@ import yaml
|
||||
from solar import extensions
|
||||
from solar import utils
|
||||
from solar.core import data
|
||||
from solar.core.resource import assign_resources_to_nodes
|
||||
from solar.core.tags_set_parser import Expression
|
||||
from solar.interfaces.db import get_db
|
||||
|
||||
# NOTE: these are extensions, they shouldn't be imported here
|
||||
@ -79,15 +82,17 @@ class Cmd(object):
|
||||
group.add_argument('-t', '--tags', nargs='+', default=['env/test_env'])
|
||||
group.add_argument('-i', '--id', default=utils.generate_uuid())
|
||||
|
||||
parser = self.subparser.add_parser('data')
|
||||
parser.set_defaults(func=getattr(self, 'data'))
|
||||
# Assign
|
||||
parser = self.subparser.add_parser('assign')
|
||||
parser.set_defaults(func=getattr(self, 'assign'))
|
||||
parser.add_argument('-n', '--nodes')
|
||||
parser.add_argument('-r', '--resources')
|
||||
|
||||
def profile(self, args):
|
||||
if args.create:
|
||||
params = {'tags': args.tags, 'id': args.id}
|
||||
profile_template_path = os.path.join(
|
||||
utils.read_config()['template-dir'], 'profile.yml'
|
||||
)
|
||||
utils.read_config()['template-dir'], 'profile.yml')
|
||||
data = yaml.load(utils.render_template(profile_template_path, params))
|
||||
self.db.store('profiles', data)
|
||||
else:
|
||||
@ -103,52 +108,31 @@ class Cmd(object):
|
||||
def discover(self, args):
|
||||
Discovery({'id': 'discovery'}).discover()
|
||||
|
||||
def data(self, args):
|
||||
def assign(self, args):
|
||||
nodes = filter(
|
||||
lambda n: Expression(args.nodes, n.get('tags', [])).evaluate(),
|
||||
self.db.get_list('nodes'))
|
||||
|
||||
resources = [
|
||||
{'id': 'mariadb',
|
||||
'tags': ['service/mariadb', 'entrypoint/mariadb'],
|
||||
'input': {
|
||||
'ip_addr': '{{ this.node.ip }}' }},
|
||||
resources = filter(
|
||||
lambda r: Expression(args.resources, r.get('tags', [])).evaluate(),
|
||||
self._get_resources_list())
|
||||
|
||||
{'id': 'keystone',
|
||||
'tags': ['service/keystone'],
|
||||
'input': {
|
||||
'name': 'keystone-test',
|
||||
'admin_port': '35357',
|
||||
'public_port': '5000',
|
||||
'db_addr': {'first_with_tags': ["entrypoint/mariadb"], 'item': '{{ item.node.ip }}'}}},
|
||||
resource_instances_path = utils.read_config()['resource-instances-path']
|
||||
utils.create_dir(resource_instances_path)
|
||||
assign_resources_to_nodes(
|
||||
resources,
|
||||
nodes,
|
||||
resource_instances_path)
|
||||
|
||||
{'id': 'haproxy',
|
||||
'tags': ['service/haproxy'],
|
||||
'input': {
|
||||
'services': [
|
||||
{'service_name': 'keystone-admin',
|
||||
'bind': '*:8080',
|
||||
'backends': {
|
||||
'with_tags': ["service/keystone"],
|
||||
'item': {'name': '{{ item.name }}', 'addr': '{{ item.node.ip }}:{{ item.admin_port }}'}}},
|
||||
|
||||
{'service_name': 'keystone-pub',
|
||||
'bind': '*:8081',
|
||||
'backends': {
|
||||
'with_tags': ["service/keystone"],
|
||||
'item': {'name': '{{ item.name }}', 'addr': '{{ item.node.ip }}:{{ item.public_port }}'}}}]}}
|
||||
]
|
||||
|
||||
nodes = [
|
||||
{'id': 'n-1',
|
||||
'ip': '10.0.0.2',
|
||||
'tags': ['node/1', 'service/keystone']},
|
||||
|
||||
{'id': 'n-2',
|
||||
'ip': '10.0.0.3',
|
||||
'tags': ['node/2', 'service/mariadb', 'service/haproxy', 'service/keystone']}]
|
||||
|
||||
dg = data.DataGraph(resources, nodes)
|
||||
|
||||
pprint.pprint(dg.resolve())
|
||||
def _get_resources_list(self):
|
||||
result = []
|
||||
for path in utils.find_by_mask(utils.read_config()['resources-files-mask']):
|
||||
resource = utils.yaml_load(path)
|
||||
resource['path'] = path
|
||||
resource['dir_path'] = os.path.dirname(path)
|
||||
result.append(resource)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -4,6 +4,8 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import yaml
|
||||
|
||||
from solar.core import actions
|
||||
@ -174,3 +176,20 @@ def load_all(dest_path):
|
||||
signals.Connections.reconnect_all()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def assign_resources_to_nodes(resources, nodes, dst_dir):
|
||||
for node in nodes:
|
||||
for resource in resources:
|
||||
merged = deepcopy(resource)
|
||||
# Node specific setting should override resource's
|
||||
merged.update(deepcopy(node))
|
||||
# Tags for specific resource is set of tags from node and from resource
|
||||
merged['tags'] = list(set(node.get('tags', [])) |
|
||||
set(resource.get('tags', [])))
|
||||
|
||||
create(
|
||||
format('{0}-{1}'.format(node['id'], resource['id'])),
|
||||
resource['dir_path'],
|
||||
dst_dir,
|
||||
merged)
|
||||
|
117
solar/solar/core/tags_set_parser.py
Normal file
117
solar/solar/core/tags_set_parser.py
Normal file
@ -0,0 +1,117 @@
|
||||
# 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 ply import lex
|
||||
from ply import yacc
|
||||
|
||||
from solar import errors
|
||||
|
||||
|
||||
tokens = (
|
||||
"STRING",
|
||||
"AND",
|
||||
"OR",
|
||||
"LPAREN",
|
||||
"RPAREN")
|
||||
|
||||
t_STRING = r'[A-Za-z0-9-_/\\]+'
|
||||
t_AND = '&|,'
|
||||
t_OR = r'\|'
|
||||
t_LPAREN = r'\('
|
||||
t_RPAREN = r'\)'
|
||||
t_ignore = ' \t\r\n'
|
||||
|
||||
|
||||
class SubexpressionWrapper(object):
|
||||
|
||||
def __init__(self, subexpression):
|
||||
self.subexpression = subexpression
|
||||
|
||||
def evaluate(self):
|
||||
self.value = self.subexpression()
|
||||
return self.value
|
||||
|
||||
def __call__(self):
|
||||
self.evaluate()
|
||||
return self.value
|
||||
|
||||
|
||||
class ScalarWrapper(object):
|
||||
def __init__(self, value):
|
||||
global expression
|
||||
self.value = (set([value]) <= set(expression.tags))
|
||||
|
||||
def evaluate(self):
|
||||
return self.value
|
||||
|
||||
def __call__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
def p_expression_logical_op(p):
|
||||
"""expression : expression AND expression
|
||||
| expression OR expression
|
||||
"""
|
||||
result, arg1, op, arg2 = p
|
||||
if op == '&' or op == ',':
|
||||
result = lambda: arg1() and arg2()
|
||||
elif op == '|':
|
||||
result = lambda: arg1() or arg2()
|
||||
|
||||
p[0] = SubexpressionWrapper(result)
|
||||
|
||||
|
||||
def p_expression_string(p):
|
||||
"""expression : STRING
|
||||
"""
|
||||
p[0] = ScalarWrapper(p[1])
|
||||
|
||||
|
||||
def p_expression_group(p):
|
||||
"""expression : LPAREN expression RPAREN
|
||||
"""
|
||||
p[0] = p[2]
|
||||
|
||||
|
||||
def t_error(t):
|
||||
errors.LexError("Illegal character '%s'" % t.value[0])
|
||||
t.lexer.skip(1)
|
||||
|
||||
|
||||
def p_error(p):
|
||||
raise errors.ParseError("Syntax error at '{0}'".format(getattr(p, 'value', '')))
|
||||
|
||||
|
||||
class Expression(object):
|
||||
def __init__(self, expression_text, tags):
|
||||
self.expression_text = expression_text
|
||||
self.tags = tags
|
||||
self.compiled_expression = parse(self)
|
||||
|
||||
def evaluate(self):
|
||||
return self.compiled_expression()
|
||||
|
||||
|
||||
lexer = lex.lex()
|
||||
parser = yacc.yacc(debug=False, write_tables=False)
|
||||
expression = None
|
||||
|
||||
|
||||
def parse(expr):
|
||||
global parser
|
||||
global expression
|
||||
# TODO it's very very bad to have global variable here
|
||||
# we should figure a way to pass it into ScalarWrapper
|
||||
expression = expr
|
||||
return parser.parse(expr.expression_text)
|
@ -9,3 +9,6 @@ class CannotFindID(SolarError):
|
||||
class CannotFindExtension(SolarError):
|
||||
pass
|
||||
|
||||
|
||||
class ParseError(SolarError):
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user