263 lines
9.4 KiB
Python
263 lines
9.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P.
|
|
#
|
|
# 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.
|
|
|
|
import logging
|
|
import operator
|
|
|
|
import monasca_analytics.banana.eval.ctx as ctx
|
|
import monasca_analytics.banana.eval.old_style as into
|
|
import monasca_analytics.banana.grammar.ast as ast
|
|
import monasca_analytics.banana.typeck.type_util as type_util
|
|
import monasca_analytics.config.connection as connection
|
|
import monasca_analytics.config.const as conf_const
|
|
import monasca_analytics.exception.banana as exception
|
|
import monasca_analytics.util.common_util as introspect
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def eval_ast(ast_root, type_table, driver):
|
|
"""
|
|
Evaluate the provided AST by instantiating
|
|
the appropriate components and connecting them
|
|
together using the Driver interface.
|
|
:type ast_root: ast.BananaFile
|
|
:param ast_root: AST to evaluate.
|
|
:type type_table: monasca_analytics.banana.typeck.type_table.TypeTable
|
|
:param type_table: the TypeTable (used to create configurations)
|
|
:type driver: monasca_analytics.spark.driver.DriverExecutor
|
|
:param driver: Driver that will manage the created
|
|
components and connect them together.
|
|
"""
|
|
logger.debug("Creating the config dictionary from the AST...")
|
|
_config = conf_const.get_default_base_config()
|
|
|
|
try:
|
|
logger.debug("Creating components according to banana config...")
|
|
components = eval_create_components(ast_root.statements, type_table)
|
|
convert_connections(ast_root.connections, _config)
|
|
logger.debug("Done creating components. Creating link data...")
|
|
# Pre-process to convert to old style components
|
|
components_old_style = into.into_old_conf_dict(components)
|
|
links = connection.connect_components(components_old_style, _config)
|
|
logger.debug("Done connecting components. Successful instantiation")
|
|
except Exception as ex:
|
|
logger.error("Failed to instantiate components")
|
|
logger.error("Reason : " + str(ex))
|
|
return
|
|
|
|
# Restart Spark using the new config
|
|
logger.debug("Stop pipeline")
|
|
driver.stop_pipeline()
|
|
logger.debug("Set new links")
|
|
driver.set_links(links)
|
|
logger.debug("Start pipeline")
|
|
driver.start_pipeline()
|
|
|
|
|
|
def convert_connections(connections, output_config):
|
|
"""
|
|
Augment the output_config object with the list of
|
|
connections
|
|
:type connections: ast.Connection
|
|
:param connections: The list of connections.
|
|
:type output_config: dict
|
|
:param output_config: Config where the links will be written.
|
|
"""
|
|
output_config[conf_const.CONNECTIONS] = connections.connections_cache
|
|
|
|
|
|
def eval_create_components(statements, type_table):
|
|
"""
|
|
Convert the provided AST into the old dict configuration.
|
|
:type statements: list[(ast.ASTNode, ast.ASTNode)]
|
|
:param statements: The AST to process
|
|
:type type_table: monasca_analytics.banana.typeck.type_table.TypeTable
|
|
:param type_table: the type table.
|
|
:rtype: dict[str, Component]
|
|
:return: Returns the component keyed by name.
|
|
"""
|
|
context = ctx.EvaluationContext()
|
|
|
|
eval_statements_generic(
|
|
statements,
|
|
type_table,
|
|
context
|
|
)
|
|
return context.get_components()
|
|
|
|
|
|
def eval_statements_generic(
|
|
statements,
|
|
type_table,
|
|
context,
|
|
cb=lambda *a, **k: None):
|
|
"""
|
|
Eval the list of statements, and call the cb after evaluating
|
|
each statement providing it with the type of the value, the
|
|
left hand side ast node, and the computed value.
|
|
:type statements: list[(ast.ASTNode, ast.ASTNode)]
|
|
:param statements: The AST to process
|
|
:type type_table: monasca_analytics.banana.typeck.type_table.TypeTable
|
|
:param type_table: the type table.
|
|
:type context: ctx.EvaluationContext
|
|
:param context: evaluation context that will collect
|
|
all intermediary results.
|
|
:type cb: (type_util.IsType, ast.ASTNode, object) -> None
|
|
:param cb: Callback called after each statement evaluation.
|
|
"""
|
|
stmt_index = 0
|
|
for stmt in statements:
|
|
lhs, rhs = stmt
|
|
expected_type = type_table.get_type(lhs, stmt_index + 1)
|
|
stmt_index += 1
|
|
# Provide the expected type
|
|
value = eval_rhs(context, rhs, expected_type)
|
|
# Call the cb with the expected_type of the value
|
|
# The lhs node and the value
|
|
cb(expected_type, lhs, value)
|
|
# Store result if referenced later.
|
|
context.set_variable(lhs, value)
|
|
|
|
|
|
def eval_rhs(context, ast_node, expected_type):
|
|
"""
|
|
Eval the right hand side node.
|
|
:type context: ctx.EvaluationContext
|
|
:param context: Evaluation context.
|
|
:type ast_node: ast.ASTNode
|
|
:param ast_node: the node to evaluate.
|
|
:type expected_type: type_util.IsType
|
|
:param expected_type: The expected type of this computation.
|
|
:return: Returns the result of this evaluation.
|
|
"""
|
|
if isinstance(ast_node, ast.StringLit):
|
|
return ast_node.inner_val()
|
|
if isinstance(ast_node, ast.Ident):
|
|
return context.get_variable(ast_node.inner_val())
|
|
if isinstance(ast_node, ast.JsonObj):
|
|
return eval_object(context, ast_node, expected_type)
|
|
if isinstance(ast_node, ast.Number):
|
|
return ast_node.val
|
|
if isinstance(ast_node, ast.DotPath):
|
|
variable_name = ast_node.varname.inner_val()
|
|
prop = map(lambda x: x.inner_val(), ast_node.properties)
|
|
return context.get_prop_of_variable(variable_name, prop)
|
|
if isinstance(ast_node, ast.Expr):
|
|
return eval_expr(context, ast_node, expected_type)
|
|
if isinstance(ast_node, ast.Component):
|
|
return eval_comp(context, ast_node, expected_type)
|
|
raise Exception("Unhandled ast value type {}!!".format(ast_node))
|
|
|
|
|
|
def eval_comp(context, comp, expected_type):
|
|
"""
|
|
Instantiate the given component, computing
|
|
the required config.
|
|
:type context: ctx.EvaluationContext
|
|
:param context: Evaluation context.
|
|
:type comp: ast.Component
|
|
:param comp: the node to evaluate.
|
|
:type expected_type: type_util.IsType
|
|
:param expected_type: The expected type of this computation.
|
|
:return: Returns the instantiated component.
|
|
"""
|
|
arguments = {}
|
|
# Compute arguments
|
|
for arg in comp.args:
|
|
arg_name = ast.DotPath(arg.arg_name.span, arg.arg_name, [])
|
|
arg_value = eval_rhs(context, arg.value, expected_type[arg_name])
|
|
arguments[arg.arg_name.inner_val()] = arg_value
|
|
# Lookup component
|
|
component_type = introspect.get_class_by_name(comp.type_name.val)
|
|
# Get default config for the component
|
|
conf = component_type.get_default_config()
|
|
# Update modified params
|
|
for k, val in arguments.iteritems():
|
|
conf[k] = val
|
|
# Delay evaluation until we do the assign
|
|
return component_type, conf
|
|
|
|
|
|
def eval_object(context, obj, expected_type):
|
|
"""
|
|
Evaluate the provided object
|
|
:type context: ctx.EvaluationContext
|
|
:param context: Evaluation context.
|
|
:type obj: ast.JsonObj
|
|
:param obj: The expression to evaluate
|
|
:type expected_type: type_util.IsType
|
|
:param expected_type: The expected type of this computation.
|
|
:return: Returns the computed value
|
|
"""
|
|
result = expected_type.default_value()
|
|
for name, val in obj.props.iteritems():
|
|
subtype = expected_type[name]
|
|
ctx.set_property(result, name, eval_rhs(context, val, subtype))
|
|
return result
|
|
|
|
|
|
def eval_expr(context, expr, expected_type):
|
|
"""
|
|
Eval the provided expression
|
|
:type context: ctx.EvaluationContext
|
|
:param context: Evaluation context.
|
|
:type expr: ast.Expr
|
|
:param expr: The expression to evaluate
|
|
:type expected_type: type_util.IsType
|
|
:param expected_type: The expected type of this computation.
|
|
:rtype: str | float
|
|
:return: Returns the computed value
|
|
"""
|
|
if len(expr.expr_tree) == 1:
|
|
return eval_rhs(context, expr.expr_tree[0], expected_type)
|
|
|
|
if isinstance(expected_type, type_util.Number):
|
|
result = 0
|
|
cast_func = float
|
|
elif isinstance(expected_type, type_util.String):
|
|
result = ""
|
|
cast_func = str
|
|
else:
|
|
raise exception.BananaEvalBug(
|
|
"Expected type for an expression can only be "
|
|
"'TypeNumber' or 'TypeString', got '{}'".format(
|
|
str(expected_type))
|
|
)
|
|
current_operator = operator.add
|
|
for el in expr.expr_tree:
|
|
if isinstance(el, basestring) and el in ['+', '-', '*', '/']:
|
|
current_operator = get_op_func(el)
|
|
else:
|
|
value = eval_rhs(context, el, expected_type)
|
|
value = cast_func(value)
|
|
result = current_operator(result, value)
|
|
return result
|
|
|
|
|
|
def get_op_func(op_str):
|
|
if op_str == '+':
|
|
return operator.add
|
|
if op_str == '-':
|
|
return operator.sub
|
|
if op_str == '*':
|
|
return operator.mul
|
|
if op_str == '/':
|
|
return operator.div
|
|
raise exception.BananaEvalBug(
|
|
"Unknown operator '{}'".format(op_str)
|
|
)
|