From 0a520fc32cef3ce0b14ace9f822109a8f4ea4a8e Mon Sep 17 00:00:00 2001 From: mkanoor Date: Mon, 10 Jul 2017 16:32:36 -0400 Subject: [PATCH 1/7] Support for expression methods --- .../miq_ae_engine/miq_ae_expression_method.rb | 147 +++++++++++++ .../engine/miq_ae_engine/miq_ae_method.rb | 9 +- .../engine/miq_ae_engine/miq_ae_object.rb | 34 +-- lib/miq_automation_engine/miq_ae_exception.rb | 8 + spec/miq_ae_expression_method_spec.rb | 197 ++++++++++++++++++ 5 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb create mode 100644 spec/miq_ae_expression_method_spec.rb diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb new file mode 100644 index 000000000..1806899c9 --- /dev/null +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb @@ -0,0 +1,147 @@ +module MiqAeEngine + class MiqAeExpressionMethod + include ApplicationController::Filter::SubstMixin + def initialize(method_obj, obj, inputs) + @edit = {} + @name = method_obj.name + @workspace = obj.workspace + @inputs = inputs + @attributes = inputs['distinct'] || inputs['attributes'] || %w(name) + load_expression(method_obj.data) + process_filter + end + + def run + @search_objects = Rbac.search(:filter => MiqExpression.new(@exp), + :class => @exp_object, + :results_format => :objects).first + @search_objects.empty? ? error_handler : set_result + end + + private + + def load_expression(data) + raise MiqAeException::MethodExpressionEmpty, "Empty expression" if data.blank? + begin + hash = YAML.load(data) + if hash[:expression] && hash[:db] + @exp = hash[:expression] + @exp_object = hash[:db] + else + raise MiqAeException::MethodExpressionInvalid, "Invalid expression #{data}" + end + rescue + raise MiqAeException::MethodExpressionInvalid, "Invalid expression #{data}" + end + end + + def process_filter + exp_table = exp_build_table(@exp) + qs_tokens = create_tokens(exp_table, @exp) + values = get_args(qs_tokens.keys.length) + values.each_with_index { |v, index| qs_tokens[index + 1][:value] = v } + exp_replace_qs_tokens(@exp, qs_tokens) + end + + def result_hash(obj) + @attributes.each_with_object({}) do |attr, hash| + hash[attr] = result_simple(obj, attr) + end + end + + def result_array + multiple = @attributes.count > 1 + result = @search_objects.collect do |obj| + multiple ? @attributes.collect { |attr| result_simple(obj, attr) } : result_simple(obj, @attributes.first) + end + @inputs['distinct'].blank? ? result : result.uniq + end + + def result_dialog_hash + key = @inputs['key'] || 'id' + @search_objects.each_with_object({}) do |obj, hash| + hash[result_simple(obj, key)] = result_simple(obj, @attributes.first) + end + end + + def result_simple(obj, attr) + raise MiqAeException::MethodNotDefined, + "Undefined method #{attr} in class #{obj.class}" unless obj.respond_to?(attr.to_sym) + obj.send(attr.to_sym) + end + + def set_result + target_object.attributes[attribute_name] = exp_value + @workspace.root['ae_result'] = 'ok' + end + + def error_handler + disposition = @inputs['on_empty'] || 'error' + case disposition.to_sym + when :error + set_error + when :warn + set_warn + when :abort + set_abort + end + end + + def set_error + $miq_ae_logger.error("Expression method ends") + @workspace.root['ae_result'] = 'error' + end + + def set_abort + $miq_ae_logger.error("Expression method aborted") + raise MiqAeException::AbortInstantiation, "Expression method #{@name} aborted" + end + + def set_warn + $miq_ae_logger.warn("Expression method ends") + @workspace.root['ae_result'] = 'warn' + set_default_value + end + + def set_default_value + return unless @inputs.key?('default') + target_object.attributes[attribute_name] = @inputs['default'] + end + + def attribute_name + @inputs['result_attr'] || 'values' + end + + def target_object + @workspace.get_obj_from_path(@inputs['result_obj'] || '.').tap do |obj| + raise MiqAeException::MethodExpressionTargetObjectMissing, @inputs['result_obj'] unless obj + end + end + + def exp_value + type = @inputs['result_type'] || 'dialog_hash' + case type.downcase.to_sym + when :hash + result_hash(@search_objects.first) + when :dialog_hash + result_dialog_hash + when :array + result_array + when :simple + result_simple(@search_objects.first, @inputs['attributes'].first) + else + raise MiqAeException::MethodExpressionResultTypeInvalid, "Invalid Result type, should be hash, array or dialog_hash" + end + end + + def get_args(num_token) + params = [] + (1..num_token).each do |i| + key = "arg#{i}" + raise MiqAeException::MethodParameterNotFound, key unless @inputs.key?(key) + params[i - 1] = @inputs[key] + end + params + end + end +end diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb index d28d76fe6..5a2ad8eb8 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb @@ -11,6 +11,11 @@ def self.invoke_inline(aem, obj, inputs) raise MiqAeException::InvalidMethod, "Inline Method Language [#{aem.language}] not supported" end + def self.invoke_expression(aem, obj, inputs) + exp_method = MiqAeEngine::MiqAeExpressionMethod.new(aem, obj, inputs) + exp_method.run + end + def self.invoke_uri(aem, obj, _inputs) scheme, userinfo, host, port, registry, path, opaque, query, fragment = URI.split(aem.data) raise MiqAeException::MethodNotFound, "Specified URI [#{aem.data}] in Method [#{aem.name}] has unsupported scheme of #{scheme}; supported scheme is file" unless scheme.downcase == "file" @@ -45,7 +50,7 @@ def self.invoke(obj, aem, args) aem.inputs.each do |f| key = f.name value = args[key] - value = obj.attributes[key] || f.default_value if value.nil? + value = obj.attributes[key] || obj.substitute_value(f.default_value) if value.nil? inputs[key] = MiqAeObject.convert_value_based_on_datatype(value, f["datatype"]) if obj.attributes[key] && f["datatype"] != "string" @@ -60,7 +65,7 @@ def self.invoke(obj, aem, args) if obj.workspace.readonly? $miq_ae_logger.info("Workspace Instantiation is READONLY -- skipping method [#{aem.fqname}] with inputs [#{inputs.inspect}]") - elsif ["inline", "builtin", "uri"].include?(aem.location.downcase.strip) + elsif ["inline", "builtin", "uri", "expression"].include?(aem.location.downcase.strip) $miq_ae_logger.info("Invoking [#{aem.location}] method [#{aem.fqname}] with inputs [#{inputs.inspect}]") return MiqAeEngine::MiqAeMethod.send("invoke_#{aem.location.downcase.strip}", aem, obj, inputs) end diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_object.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_object.rb index a04826a5c..38bbeffa6 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_object.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_object.rb @@ -427,6 +427,23 @@ def uri2value(uri, required = false) uri # if it was not processed, return the original uri end + def substitute_value(value, _type = nil, required = false) + Benchmark.current_realtime[:substitution_count] += 1 + Benchmark.realtime_block(:substitution_time) do + value = value.gsub(RE_SUBST) do |_s| + subst = uri2value($1, required) + subst &&= subst.to_s + # This encoding of relationship is not needed, until we can get a valid use case + # Based on RFC 3986 Section 2.4 "When to Encode or Decode" + # We are properly encoding when we send URL requests to external systems + # or building an automate request + # subst &&= URI.escape(subst, RE_URI_ESCAPE) if type == :aetype_relationship + subst + end unless value.nil? + return value + end + end + private def call_method(obj, method) @@ -541,23 +558,6 @@ def self.convert_value_based_on_datatype(value, datatype) value end - def substitute_value(value, _type = nil, required = false) - Benchmark.current_realtime[:substitution_count] += 1 - Benchmark.realtime_block(:substitution_time) do - value = value.gsub(RE_SUBST) do |_s| - subst = uri2value($1, required) - subst &&= subst.to_s - # This encoding of relationship is not needed, until we can get a valid use case - # Based on RFC 3986 Section 2.4 "When to Encode or Decode" - # We are properly encoding when we send URL requests to external systems - # or building an automate request - # subst &&= URI.escape(subst, RE_URI_ESCAPE) if type == :aetype_relationship - subst - end unless value.nil? - return value - end - end - def process_assertion(f, message, args) Benchmark.current_realtime[:assertion_count] += 1 Benchmark.realtime_block(:assertion_time) do diff --git a/lib/miq_automation_engine/miq_ae_exception.rb b/lib/miq_automation_engine/miq_ae_exception.rb index c7fb22e9e..ae6b81953 100644 --- a/lib/miq_automation_engine/miq_ae_exception.rb +++ b/lib/miq_automation_engine/miq_ae_exception.rb @@ -39,6 +39,14 @@ class FileExists < MiqAeDatastoreError; end class DomainNotAccessible < MiqAeDatastoreError; end class CannotLock < MiqAeDatastoreError; end class CannotUnlock < MiqAeDatastoreError; end + + class MethodExpressionNotFound < MiqAeEngineError; end + class MethodExpressionEmpty < MiqAeEngineError; end + class MethodExpressionInvalid < MiqAeEngineError; end + class MethodExpressionTargetObjectMissing < MiqAeEngineError; end + class MethodExpressionResultTypeInvalid < MiqAeEngineError; end + class MethodParameterNotFound < MiqAeEngineError; end + class MethodNotDefined < MiqAeEngineError; end end module MiqException diff --git a/spec/miq_ae_expression_method_spec.rb b/spec/miq_ae_expression_method_spec.rb new file mode 100644 index 000000000..79f6f1e4f --- /dev/null +++ b/spec/miq_ae_expression_method_spec.rb @@ -0,0 +1,197 @@ +module MiqAeExpressionMethodSpec + include MiqAeEngine + describe MiqAeExpressionMethod do + include Spec::Support::AutomationHelper + let(:user) { FactoryGirl.create(:user_with_group) } + let(:vm1) { FactoryGirl.create(:vm, :name => 'test_2.1', :cpu_shares => 400) } + let(:vm2) { FactoryGirl.create(:vm, :name => 'test_3.1', :cpu_shares => 400) } + let(:vm3) { FactoryGirl.create(:vm, :name => 'test_4.2', :cpu_shares => 11) } + let(:complex_qs_exp) do + {"and" => [{"STARTS WITH" => {"field" => "Vm-name", "value" => :user_input}}, + {"ENDS WITH" => {"field" => "Vm-name", "value" => :user_input}}] + } + end + + let(:m_params) do + {'arg1' => {'datatype' => 'string', 'default_value' => 'test'}, + 'arg2' => {'datatype' => 'string', 'default_value' => '1'}, + 'attributes' => {'datatype' => 'array', 'default_value' => 'name'} + } + end + + let(:vm_search) do + {:db => 'Vm', + :expression => complex_qs_exp}.to_yaml + end + + before do + allow(User).to receive(:server_timezone).and_return("UTC") + end + + it "expression_method" do + vm1 + vm2 + vm3 + m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['values']).to match_array(%w(test_2.1 test_3.1)) + end + + it "expression_method dialog_hash" do + vm1 + vm2 + vm3 + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['values'].keys).to match_array([vm1.id, vm2.id]) + expect(ws.root.attributes['values'].values).to match_array([vm1.name, vm2.name]) + end + + it "expression_method no result" do + vm1 + vm2 + vm3 + m_params['arg1'] = {'datatype' => 'string', 'default_value' => 'nada'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['ae_result']).to eq('error') + end + + it "expression_method no result use default value" do + vm1 + vm2 + vm3 + m_params['arg1'] = {'datatype' => 'string', 'default_value' => 'nada'} + m_params['default'] = {'datatype' => 'array', 'default_value' => 'nada'} + m_params['on_empty'] = {'datatype' => 'string', 'default_value' => 'warn'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['ae_result']).to eq('warn') + expect(ws.root.attributes['values']).to match_array(%w(nada)) + end + + it "expression_method result attr" do + vm1 + vm2 + vm3 + m_params['result_attr'] = {'datatype' => 'string', 'default_value' => 'vitalstatistix'} + m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['vitalstatistix']).to match_array(%w(test_2.1 test_3.1)) + end + + it "expression_method distinct" do + vm1 + vm2 + vm3 + m_params['distinct'] = {'datatype' => 'array', 'default_value' => 'cpu_shares'} + m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + ws = MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + + expect(ws.root.attributes['values']).to match_array([400]) + end + + it "expression_method undefined function" do + vm1 + vm2 + m_params['attributes'] = {'datatype' => 'array', 'default_value' => 'nada'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + + expect do + MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + end.to raise_error(MiqAeException::MethodNotDefined) + end + + it "expression_method target object missing" do + vm1 + vm2 + vm3 + m_params['result_obj'] = {'datatype' => 'string', 'default_value' => 'nada'} + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + + expect do + MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + end.to raise_error(MiqAeException::MethodExpressionTargetObjectMissing) + end + + it "expression_method invalid result type" do + vm1 + vm2 + vm3 + m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'nada'} + + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => m_params, + :method_loc => 'expression', + :method_script => vm_search) + expect do + MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + end.to raise_error(MiqAeException::MethodExpressionResultTypeInvalid) + end + + it "not all parameters provided" do + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => {}, + :method_loc => 'expression', + :method_script => vm_search) + + expect do + MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + end.to raise_error(MiqAeException::MethodParameterNotFound) + end + + it "invalid search name" do + create_ae_model_with_method(:ae_namespace => 'GAULS', + :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', + :method_name => 'OBELIX', :method_params => {}, + :method_loc => 'expression', + :method_script => "nada") + + expect do + MiqAeEngine.instantiate('/GAULS/ASTERIX/DOGMATIX', user) + end.to raise_error(MiqAeException::MethodExpressionInvalid) + end + end +end From b1e2ae9086bb7bb324963847e606a8ce15044fb0 Mon Sep 17 00:00:00 2001 From: mkanoor Date: Tue, 11 Jul 2017 10:19:00 -0400 Subject: [PATCH 2/7] Rubocop warnings --- .../engine/miq_ae_engine/miq_ae_expression_method.rb | 2 +- .../engine/miq_ae_engine/miq_ae_method.rb | 2 +- spec/miq_ae_expression_method_spec.rb | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb index 1806899c9..1dcb4f833 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb @@ -23,7 +23,7 @@ def run def load_expression(data) raise MiqAeException::MethodExpressionEmpty, "Empty expression" if data.blank? begin - hash = YAML.load(data) + hash = YAML.safe_load(data) if hash[:expression] && hash[:db] @exp = hash[:expression] @exp_object = hash[:db] diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb index 5a2ad8eb8..c8cb6c93c 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_method.rb @@ -65,7 +65,7 @@ def self.invoke(obj, aem, args) if obj.workspace.readonly? $miq_ae_logger.info("Workspace Instantiation is READONLY -- skipping method [#{aem.fqname}] with inputs [#{inputs.inspect}]") - elsif ["inline", "builtin", "uri", "expression"].include?(aem.location.downcase.strip) + elsif %w(inline builtin uri expression).include?(aem.location.downcase.strip) $miq_ae_logger.info("Invoking [#{aem.location}] method [#{aem.fqname}] with inputs [#{inputs.inspect}]") return MiqAeEngine::MiqAeMethod.send("invoke_#{aem.location.downcase.strip}", aem, obj, inputs) end diff --git a/spec/miq_ae_expression_method_spec.rb b/spec/miq_ae_expression_method_spec.rb index 79f6f1e4f..f3003737d 100644 --- a/spec/miq_ae_expression_method_spec.rb +++ b/spec/miq_ae_expression_method_spec.rb @@ -8,15 +8,13 @@ module MiqAeExpressionMethodSpec let(:vm3) { FactoryGirl.create(:vm, :name => 'test_4.2', :cpu_shares => 11) } let(:complex_qs_exp) do {"and" => [{"STARTS WITH" => {"field" => "Vm-name", "value" => :user_input}}, - {"ENDS WITH" => {"field" => "Vm-name", "value" => :user_input}}] - } + {"ENDS WITH" => {"field" => "Vm-name", "value" => :user_input}}]} end let(:m_params) do {'arg1' => {'datatype' => 'string', 'default_value' => 'test'}, 'arg2' => {'datatype' => 'string', 'default_value' => '1'}, - 'attributes' => {'datatype' => 'array', 'default_value' => 'name'} - } + 'attributes' => {'datatype' => 'array', 'default_value' => 'name'}} end let(:vm_search) do From 5a6db7b51a925e5b20ff671c519b1256b649ff3b Mon Sep 17 00:00:00 2001 From: mkanoor Date: Tue, 11 Jul 2017 12:04:51 -0400 Subject: [PATCH 3/7] Removed safe_load --- .../engine/miq_ae_engine/miq_ae_expression_method.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb index 1dcb4f833..1806899c9 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb @@ -23,7 +23,7 @@ def run def load_expression(data) raise MiqAeException::MethodExpressionEmpty, "Empty expression" if data.blank? begin - hash = YAML.safe_load(data) + hash = YAML.load(data) if hash[:expression] && hash[:db] @exp = hash[:expression] @exp_object = hash[:db] From fc2194032db34954400fe63eb3c869eafb0cf49d Mon Sep 17 00:00:00 2001 From: mkanoor Date: Tue, 11 Jul 2017 16:26:27 -0400 Subject: [PATCH 4/7] Changed let to let! --- spec/miq_ae_expression_method_spec.rb | 32 +++------------------------ 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/spec/miq_ae_expression_method_spec.rb b/spec/miq_ae_expression_method_spec.rb index f3003737d..d158429d2 100644 --- a/spec/miq_ae_expression_method_spec.rb +++ b/spec/miq_ae_expression_method_spec.rb @@ -3,9 +3,9 @@ module MiqAeExpressionMethodSpec describe MiqAeExpressionMethod do include Spec::Support::AutomationHelper let(:user) { FactoryGirl.create(:user_with_group) } - let(:vm1) { FactoryGirl.create(:vm, :name => 'test_2.1', :cpu_shares => 400) } - let(:vm2) { FactoryGirl.create(:vm, :name => 'test_3.1', :cpu_shares => 400) } - let(:vm3) { FactoryGirl.create(:vm, :name => 'test_4.2', :cpu_shares => 11) } + let!(:vm1) { FactoryGirl.create(:vm, :name => 'test_2.1', :cpu_shares => 400) } + let!(:vm2) { FactoryGirl.create(:vm, :name => 'test_3.1', :cpu_shares => 400) } + let!(:vm3) { FactoryGirl.create(:vm, :name => 'test_4.2', :cpu_shares => 11) } let(:complex_qs_exp) do {"and" => [{"STARTS WITH" => {"field" => "Vm-name", "value" => :user_input}}, {"ENDS WITH" => {"field" => "Vm-name", "value" => :user_input}}]} @@ -27,9 +27,6 @@ module MiqAeExpressionMethodSpec end it "expression_method" do - vm1 - vm2 - vm3 m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} create_ae_model_with_method(:ae_namespace => 'GAULS', :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', @@ -42,9 +39,6 @@ module MiqAeExpressionMethodSpec end it "expression_method dialog_hash" do - vm1 - vm2 - vm3 create_ae_model_with_method(:ae_namespace => 'GAULS', :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', :method_name => 'OBELIX', :method_params => m_params, @@ -57,9 +51,6 @@ module MiqAeExpressionMethodSpec end it "expression_method no result" do - vm1 - vm2 - vm3 m_params['arg1'] = {'datatype' => 'string', 'default_value' => 'nada'} create_ae_model_with_method(:ae_namespace => 'GAULS', :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', @@ -72,9 +63,6 @@ module MiqAeExpressionMethodSpec end it "expression_method no result use default value" do - vm1 - vm2 - vm3 m_params['arg1'] = {'datatype' => 'string', 'default_value' => 'nada'} m_params['default'] = {'datatype' => 'array', 'default_value' => 'nada'} m_params['on_empty'] = {'datatype' => 'string', 'default_value' => 'warn'} @@ -90,9 +78,6 @@ module MiqAeExpressionMethodSpec end it "expression_method result attr" do - vm1 - vm2 - vm3 m_params['result_attr'] = {'datatype' => 'string', 'default_value' => 'vitalstatistix'} m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} create_ae_model_with_method(:ae_namespace => 'GAULS', @@ -106,9 +91,6 @@ module MiqAeExpressionMethodSpec end it "expression_method distinct" do - vm1 - vm2 - vm3 m_params['distinct'] = {'datatype' => 'array', 'default_value' => 'cpu_shares'} m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'array'} create_ae_model_with_method(:ae_namespace => 'GAULS', @@ -122,8 +104,6 @@ module MiqAeExpressionMethodSpec end it "expression_method undefined function" do - vm1 - vm2 m_params['attributes'] = {'datatype' => 'array', 'default_value' => 'nada'} create_ae_model_with_method(:ae_namespace => 'GAULS', :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', @@ -137,9 +117,6 @@ module MiqAeExpressionMethodSpec end it "expression_method target object missing" do - vm1 - vm2 - vm3 m_params['result_obj'] = {'datatype' => 'string', 'default_value' => 'nada'} create_ae_model_with_method(:ae_namespace => 'GAULS', :ae_class => 'ASTERIX', :instance_name => 'DOGMATIX', @@ -153,9 +130,6 @@ module MiqAeExpressionMethodSpec end it "expression_method invalid result type" do - vm1 - vm2 - vm3 m_params['result_type'] = {'datatype' => 'string', 'default_value' => 'nada'} create_ae_model_with_method(:ae_namespace => 'GAULS', From f659e9877ca915850e70258fd70262e985f59a9b Mon Sep 17 00:00:00 2001 From: mkanoor Date: Fri, 14 Jul 2017 09:00:05 -0400 Subject: [PATCH 5/7] Fixed issues when no user_input in fields --- .../engine/miq_ae_engine/miq_ae_expression_method.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb index 1806899c9..78879119b 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb @@ -36,10 +36,10 @@ def load_expression(data) end def process_filter - exp_table = exp_build_table(@exp) + exp_table = exp_build_table(@exp, true) qs_tokens = create_tokens(exp_table, @exp) values = get_args(qs_tokens.keys.length) - values.each_with_index { |v, index| qs_tokens[index + 1][:value] = v } + qs_tokens.keys.each_with_index { |token_at, index| qs_tokens[token_at][:value] = values[index] } exp_replace_qs_tokens(@exp, qs_tokens) end From 7aa3d962de1a7382fa1bf214ef374ad2dd8f85a9 Mon Sep 17 00:00:00 2001 From: mkanoor Date: Fri, 14 Jul 2017 15:33:00 -0400 Subject: [PATCH 6/7] Fixed Rubocop warnings --- .../engine/miq_ae_engine/miq_ae_expression_method.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb index 78879119b..35e6c36d9 100644 --- a/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb +++ b/lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_expression_method.rb @@ -39,7 +39,7 @@ def process_filter exp_table = exp_build_table(@exp, true) qs_tokens = create_tokens(exp_table, @exp) values = get_args(qs_tokens.keys.length) - qs_tokens.keys.each_with_index { |token_at, index| qs_tokens[token_at][:value] = values[index] } + qs_tokens.keys.each_with_index { |token_at, index| qs_tokens[token_at][:value] = values[index] } exp_replace_qs_tokens(@exp, qs_tokens) end @@ -65,8 +65,7 @@ def result_dialog_hash end def result_simple(obj, attr) - raise MiqAeException::MethodNotDefined, - "Undefined method #{attr} in class #{obj.class}" unless obj.respond_to?(attr.to_sym) + raise MiqAeException::MethodNotDefined, "Undefined method #{attr} in class #{obj.class}" unless obj.respond_to?(attr.to_sym) obj.send(attr.to_sym) end From b557ecfe899752234653d745b1473c146410e122 Mon Sep 17 00:00:00 2001 From: mkanoor Date: Tue, 11 Jul 2017 15:23:57 -0400 Subject: [PATCH 7/7] Export/Import of expressions The expressions are stored in the data section of yaml --- app/models/miq_ae_yaml_export.rb | 8 ++- app/models/miq_ae_yaml_import.rb | 3 +- spec/models/miq_ae_yaml_import_export_spec.rb | 60 ++++++++++++++----- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/app/models/miq_ae_yaml_export.rb b/app/models/miq_ae_yaml_export.rb index 95a07e6e4..0ec69b6f1 100644 --- a/app/models/miq_ae_yaml_export.rb +++ b/app/models/miq_ae_yaml_export.rb @@ -168,7 +168,9 @@ def write_all_methods(ns_fqname, class_obj) class_obj.ae_methods.sort_by(&:fqname).each do |meth_obj| export_file_hash['created_on'] = meth_obj.created_on export_file_hash['updated_on'] = meth_obj.updated_on - write_method_file(meth_obj, export_file_hash) unless meth_obj.location == 'builtin' + if meth_obj.location == 'inline' + write_method_file(meth_obj, export_file_hash) + end write_method_attributes(meth_obj, export_file_hash) end end @@ -176,7 +178,9 @@ def write_all_methods(ns_fqname, class_obj) def write_method_attributes(method_obj, export_file_hash) envelope_hash = setup_envelope(method_obj, METHOD_OBJ_TYPE) envelope_hash['object']['inputs'] = method_obj.method_inputs - envelope_hash['object']['attributes'].delete('data') + if method_obj.location == "inline" + envelope_hash['object']['attributes'].delete('data') + end if method_obj.embedded_methods.empty? envelope_hash['object']['attributes'].delete('embedded_methods') end diff --git a/app/models/miq_ae_yaml_import.rb b/app/models/miq_ae_yaml_import.rb index 72a68ee32..231ac5865 100644 --- a/app/models/miq_ae_yaml_import.rb +++ b/app/models/miq_ae_yaml_import.rb @@ -217,7 +217,8 @@ def process_instance(class_obj, instance_yaml) def process_method(class_obj, ruby_method_file_name, method_yaml) method_attributes = method_yaml.fetch_path('object', 'attributes') if method_attributes['location'] == 'inline' - method_yaml.store_path('object', 'attributes', 'data', load_method_ruby(ruby_method_file_name)) + data = load_method_ruby(ruby_method_file_name) + method_yaml.store_path('object', 'attributes', 'data', data) if data end method_obj = MiqAeMethod.find_by(:name => method_attributes['name'], :class_id => class_obj.id) unless class_obj.nil? track_stats('method', method_obj) diff --git a/spec/models/miq_ae_yaml_import_export_spec.rb b/spec/models/miq_ae_yaml_import_export_spec.rb index 049c7e463..bcf984daf 100644 --- a/spec/models/miq_ae_yaml_import_export_spec.rb +++ b/spec/models/miq_ae_yaml_import_export_spec.rb @@ -6,13 +6,14 @@ 'max_retries' => "10", 'collect' => "dinosaurs", 'max_time' => "100"} + @expression_data = "---\n:db: VmOrTemplate\n:expression:\n STARTS WITH:\n field: VmOrTemplate-name\n value: :user_input\n" @clear_default_password = 'little_secret' @clear_password = 'secret' @relations_value = "bedrock relations" @domain_counts = {'dom' => 1, 'ns' => 3, 'class' => 4, 'inst' => 10, - 'meth' => 3, 'field' => 12, 'value' => 8} + 'meth' => 4, 'field' => 12, 'value' => 8} @domain_counts_with_extra_items = {'dom' => 1, 'ns' => 4, 'class' => 5, 'inst' => 11, - 'meth' => 3, 'field' => 12, 'value' => 8} + 'meth' => 4, 'field' => 12, 'value' => 8} EvmSpecHelper.local_miq_server @tenant = Tenant.seed create_factory_data("manageiq", 0, MiqAeDomain::SYSTEM_SOURCE) @@ -225,7 +226,7 @@ def assert_all_domains_imported(export_options, import_options) export_model(MiqAeYamlImportExportMixin::ALL_DOMAINS, export_options) reset_and_import(@export_dir, MiqAeYamlImportExportMixin::ALL_DOMAINS, import_options) check_counts('dom' => 2, 'ns' => 6, 'class' => 8, 'inst' => 20, - 'meth' => 6, 'field' => 24, 'value' => 16) + 'meth' => 8, 'field' => 24, 'value' => 16) end it "import single domain, from directory" do @@ -370,7 +371,7 @@ def assert_import_as(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) check_counts('dom' => 2, 'ns' => 6, 'class' => 8, 'inst' => 20, - 'meth' => 6, 'field' => 24, 'value' => 16) + 'meth' => 8, 'field' => 24, 'value' => 16) expect(MiqAeDomain.find_by_fqname(import_options['import_as'])).not_to be_nil end @@ -419,7 +420,7 @@ def assert_import_namespace_only(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) check_counts('dom' => 1, 'ns' => 2, 'class' => 3, 'inst' => 6, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) end it "domain, import only multi-part namespace, to directory" do @@ -470,7 +471,7 @@ def assert_import_class_only(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 2, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) end it "namespace, to directory" do @@ -495,7 +496,7 @@ def assert_single_namespace_export(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) check_counts('dom' => 1, 'ns' => 2, 'class' => 3, 'inst' => 6, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) end it "namespace, multi-part, to directory" do @@ -528,7 +529,7 @@ def assert_multi_namespace_export(export_options, import_options) export_model(@manageiq_domain.name, options) reset_and_import(@export_dir, @manageiq_domain.name) check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 2, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) @manageiq_domain = MiqAeNamespace.find_by_fqname('manageiq', false) @aen1_aec1 = MiqAeClass.find_by_name('manageiq_test_class_1') @aen1_aec1_aei2 = FactoryGirl.create(:miq_ae_instance, @@ -538,7 +539,7 @@ def assert_multi_namespace_export(export_options, import_options) export_model(@manageiq_domain.name, options) MiqAeImport.new(@manageiq_domain.name, 'preview' => false, 'import_dir' => @export_dir).import check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 3, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) end it "class, with methods, to directory" do @@ -565,7 +566,7 @@ def assert_class_with_methods_export(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 2, - 'meth' => 2, 'field' => 6, 'value' => 4) + 'meth' => 3, 'field' => 6, 'value' => 4) end it "class, with builtin methods, as directory" do @@ -588,15 +589,35 @@ def assert_class_with_methods_export(export_options, import_options) assert_class_with_builtin_methods_export(export_options, import_options) end - def assert_class_with_builtin_methods_export(export_options, import_options) + it "class, with expression methods, as zip" do + export_options = {'namespace' => @aen1.name, 'class' => @aen1_aec1.name} + export_options['zip_file'] = @zip_file + import_options = {'zip_file' => @zip_file} + assert_class_with_expression_methods_export(export_options, import_options) + end + + def assert_methods_export(export_options, import_options) export_model(@manageiq_domain.name, export_options) reset_and_import(@export_dir, @manageiq_domain.name, import_options) - check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 2, - 'meth' => 2, 'field' => 6, 'value' => 4) + check_counts('dom' => 1, 'ns' => 1, 'class' => 1, 'inst' => 2, + 'meth' => 3, 'field' => 6, 'value' => 4) + end + + def assert_class_with_builtin_methods_export(export_options, import_options) + assert_methods_export(export_options, import_options) + assert_method_data('test2', 'builtin', nil) + end + + def assert_class_with_expression_methods_export(export_options, import_options) + assert_methods_export(export_options, import_options) + assert_method_data('test3', 'expression', @expression_data) + end + + def assert_method_data(name, location, data) aen1_aec1 = MiqAeClass.find_by_name('manageiq_test_class_1') - builtin_method = MiqAeMethod.find_by_class_id_and_name(aen1_aec1.id, 'test2') - expect(builtin_method.location).to eql 'builtin' - expect(builtin_method.data).to be_nil + method = MiqAeMethod.find_by_class_id_and_name(aen1_aec1.id, name) + expect(method.location).to eql location + expect(method.data).to eq(data) end it "class, without methods, to directory" do @@ -771,6 +792,13 @@ def create_factory_data(domain_name, priority, source = MiqAeDomain::USER_SOURCE :scope => "instance", :language => "ruby", :location => "builtin") + FactoryGirl.create(:miq_ae_method, + :class_id => n1_c1.id, + :name => 'test3', + :scope => "instance", + :language => "ruby", + :data => @expression_data, + :location => "expression") FactoryGirl.create(:miq_ae_instance, :name => 'test_instance2', :class_id => n1_c1.id) create_fields(n1_c1, n1_c1_i1, n1_c1_m1)