Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alert Definitions: add hash_expression #76

Merged
merged 1 commit into from
Dec 19, 2017

Conversation

zakiva
Copy link
Contributor

@zakiva zakiva commented Sep 26, 2017

Depends on: ManageIQ/manageiq#16397

Changes included:

  • Currently, it is possible to create an alert definition via API only with an expression as MiqExpression. Following the changes in Add hash_expression to MiqAlert manageiq#15315, this PR adds the ability to create an alert with hash expression.

  • Backward Compatibility: we would like to keep the option to create an alert with expression as MiqExpression without explicitly passing a miq_expression attribute. So the user will have now 3 options when sending a create request and will need to pass exactly one of the following:

  1. expression which will be saved as MiqExpression (same as before).
  2. miq_expression.
  3. hash_expression.
  • Allowing editing an alert definition (with similar logic).

  • Deep symbolize for data.

(This PR replaces ManageIQ/manageiq#14979 with different implementation)

@zakiva
Copy link
Contributor Author

zakiva commented Sep 26, 2017

@miq-bot add_label enhancement

@zakiva
Copy link
Contributor Author

zakiva commented Sep 26, 2017

@imtayadeway @moolitayer Please review
cc @simon3z @ilackarms

raise BadRequestError, "Must not specify more than one expression for editing a #{type} resource"
elsif exp_values.size == 1
if data["hash_expression"]
data["miq_expression"] = nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as we discussed with @zakiva this handles an edge case where a user wants to replace a hash expression with a miq expression or vice versa. Honestly I don't know if we should support that

@moolitayer
Copy link

@imtayadeway @abellotti We need an API review here.

@zakiva zakiva force-pushed the alert_hash_expression branch 2 times, most recently from c6c6671 to ba951e7 Compare September 26, 2017 13:59
@zakiva zakiva changed the title Alert Definitions: add hash_expression and symbolize options Alert Definitions: add hash_expression Sep 26, 2017
@ilackarms
Copy link
Contributor

@zakiva can you provide an example of a curl / HTTP request to add an alert definition?

@zakiva
Copy link
Contributor Author

zakiva commented Oct 1, 2017

@zakiva can you provide an example of a curl / HTTP request to add an alert definition?

curl -X POST -H "Content-Type: application/json" -d "@alert.json" http://localhost:3000/api/alert_definitions --user admin:smartvm

example of alert.json file:

{
   "action" : "create",
   "resource" : {
   	"description" : "Alert Description",
   	"db": "ContainerNode",
   	"miq_expression": {
   		"mode": "internal",
   		"options": {
   			},
   		"eval_method": "hostd_log_threshold"
   	},
   	"options": {
   		"notifications": {
   			"email": {
   				"from": "",
                                "to": []
            }
          }
        }
   }
}

you can use expression/miq_expression/hash_expression as you like.

@zakiva
Copy link
Contributor Author

zakiva commented Oct 1, 2017

ping @imtayadeway Can you please review?

@zakiva
Copy link
Contributor Author

zakiva commented Oct 23, 2017

ping @abellotti @imtayadeway


before_action :set_additional_attributes

def create_resource(type, id, data = {})
assert_id_not_specified(data, type)
assert_all_required_fields_exists(data, type, REQUIRED_FIELDS)
if expression_values(data).size != 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like business logic to me. What's more - it's duplicated on line 26. This should be added to the model as a validation, which will greatly simplify this code. The code here should be almost entirely concerned with deserializing the JSON request body and passing data to the model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed that, will add a validation in the model later on.

@@ -1,26 +1,39 @@
module Api
class AlertDefinitionsController < BaseController
REQUIRED_FIELDS = %w(description db expression options).freeze
REQUIRED_FIELDS = %w(description db options).freeze
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that you're only changing this part - but I'm also confused as to why these "required fields" aren't expressed in the model too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I can remove them in this PR and then add the validations in the model.

@zakiva zakiva force-pushed the alert_hash_expression branch from ba951e7 to 9a06176 Compare October 25, 2017 13:02
@zakiva
Copy link
Contributor Author

zakiva commented Nov 1, 2017

@imtayadeway PTAL

Copy link
Contributor

@imtayadeway imtayadeway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zakiva this looks better 😁 but I think there is still too much here that could be pushed further down into the model for better encapsulation

@@ -1,26 +1,31 @@
module Api
class AlertDefinitionsController < BaseController
REQUIRED_FIELDS = %w(description db expression options).freeze

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can nix this empty line too 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :-)

begin
data["expression"] = MiqExpression.new(data["expression"])
create_miq_expression(data)
data["enabled"] = true if data["enabled"].nil?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I know you're not touching this here (and feel free to leave for a follow up if appropriate) but this is more business logic - "an alert definition is enabled by default" - that belongs in the model

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, removing that too. This should be handled explicitly by the user.
cc @moolitayer @elad661

end

def create_miq_expression(data)
data["expression"] = MiqExpression.new(data["expression"]) if data["expression"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this is necessary. Type coercion should be handled by the setter in the model - if that's not currently the case then that needs to be added so we don't have to munge the data here (and anywhere else for that matter).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, moved it to the model: ManageIQ/manageiq#16397

rescue => err
raise BadRequestError, "Failed to create a new alert definition - #{err}"
end
end

def edit_resource(type, id = nil, data = {})
raise BadRequestError, "Must specify an id for editing a #{type} resource" unless id
if expression_values(data).size == 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed? Again, this is another business rule that needs to be encapsulated in the model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove that too.
The intention here was to handle the case of replacing a miq expression with hash expression (and vice versa) when editing an alert, but we can leave that to the user to explicitly nullify these fields.
cc @moolitayer @elad661

data["enabled"] = true if data["enabled"].nil?
super(type, id, data).serializable_hash.merge("expression" => data["expression"])
super(type, id, data.deep_symbolize_keys).serializable_hash.merge("expression" => expression_values(data).first)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the deep_symbolize_keys needed? If it's for a particular serialized column that is keyed with symbols, I think it would be better to make that explicit rather than converting everything wholesale

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want the created alerts via API to be identical to alerts created in other places.
It is not a particular (single) column, so I think it will be nicer not to list them.

@zakiva zakiva force-pushed the alert_hash_expression branch 2 times, most recently from 9669ae4 to 79bbc02 Compare November 6, 2017 11:57
@zakiva
Copy link
Contributor Author

zakiva commented Nov 6, 2017

@imtayadeway Thanks for the review! I made the requested changes.

@elad661
Copy link
Contributor

elad661 commented Nov 19, 2017

@imtayadeway now that the change in the model ( ManageIQ/manageiq#16397 ) is merged, can we get this PR merged too, or are there any other required changes?

@bazulay
Copy link

bazulay commented Nov 21, 2017

@imtayadeway @abellotti PTAL

@elad661
Copy link
Contributor

elad661 commented Dec 4, 2017

@imtayadeway Can you please take a look? This PR has been waiting for a re-review for almost a month now...

@bazulay
Copy link

bazulay commented Dec 4, 2017

@gtanzillo ping

Copy link
Contributor

@imtayadeway imtayadeway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zakiva looking good, but there is some surprising behavior that I've outlined above that I think we need to address before this is merged. Thanks!

@@ -31,5 +28,10 @@ def edit_resource(type, id = nil, data = {})
def set_additional_attributes
@additional_attributes = %w(expression)
end

def update_miq_expression(data)
data["miq_expression"] = data["expression"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure why this is needed. Doesn't the model support this via both #expression= and #miq_expression= ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because the setter in the model for expression= looks like this: https://github.com/ManageIQ/manageiq/blob/master/app/models/miq_alert.rb#L59-L65

    if exp.kind_of?(MiqExpression)
      self.miq_expression = exp
    elsif exp.kind_of?(Hash)
      self.hash_expression = exp

And the data we get from the api user is always a "hash" (but not a Hash Expression), because it's parsed json.

The model will only try to create a new MiqExpression from hash data if you explicitly use the miq_expression setter, so we need to check if the user provided us an "expression" parameter, and if so explicitly say we want a miq_expression and not an hash expression.

Since hash expressions are just ruby hashes, there's no cleaner way to do this - I don't think we'd want the model to attempt parsing hashes to see if they fit the "miq expression" or "hash expression" forms and magically decide which type to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK for now....can we look at deprecating the old API (i.e. MiqAlert#expression) in a follow up PR? Then I think a lot of the complexity of this controller could go away.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with deprecating the MiqAlert#expression, and I can do a follow-up PR.

@moolitayer is there any reason to keep the old MiqAlert#expression API around?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the model we would need to keep the expression due to the alerts screen.
In the API I agree we should deprecate and then drop.
(deprecate in Gaprindashvili and Drop in H?)

data["enabled"] = true if data["enabled"].nil?
super(type, id, data).serializable_hash.merge("expression" => data["expression"])
update_miq_expression(data) if data["expression"]
alert = super(type, id, data.deep_symbolize_keys).serializable_hash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned that this deep_symbolize_keys will corrupt an incoming MiqExpression:

{"=" => {"field" => "foo", "value" => "bar"}}.deep_symbolize_keys # => {:"=" => {:field => "foo", :value => "bar"}}

@@ -81,7 +81,7 @@
alert_definition = MiqAlert.find(response.parsed_body["results"].first["id"])
expect(alert_definition).to be_truthy
expect(alert_definition.expression.class).to eq(MiqExpression)
expect(alert_definition.expression.exp).to eq(sample_alert_definition["expression"])
expect(alert_definition.expression.exp).to eq(sample_alert_definition["expression"].deep_symbolize_keys)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong, because miq expressions are not keyed with symbols (see above)

alert_definition = MiqAlert.find(response.parsed_body["results"].first["id"])
expect(alert_definition).to be_truthy
expect(alert_definition.expression.class).to eq(MiqExpression)
expect(alert_definition.expression.exp).to eq(sample_alert_definition["miq_expression"].deep_symbolize_keys)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

sample_alert_definition = {
"description" => "Test Alert Definition",
"db" => "ContainerNode",
"miq_expression" => { "eval_method" => "dwh_generic", "mode" => "internal", "options" => {} },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be a hash expression standing in place of an miq expression? Even if this "works", I wouldn't want us to officially support this surprising behavior

"description" => "Test Alert Definition",
"db" => "ContainerNode",
"hash_expression" => { "eval_method" => "nothing", "mode" => "internal", "options" => {} },
"miq_expression" => { "eval_method" => "dwh_generic", "mode" => "internal", "options" => {} },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

api_alert_definition_url(nil, alert_definition),
:params => {
:action => "edit",
:miq_expression => exp,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

:params => {
:action => "edit",
:hash_expression => { :eval_method => "event_threshold", :mode => "internal", :options => {} },
:miq_expression => { :eval_method => "mw_heap_used", :mode => "internal", :options => {} }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is still a "hash_expression" in place of an "miq_expression"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, you're right, I didn't actually fix this one when I said it was. I fixed it now.

@elad661
Copy link
Contributor

elad661 commented Dec 6, 2017

Thanks for the review. I fixed the tests and changed the controller to only do .deep_symbolize_keys on hash expressions (since those do need to be symbolized).

@elad661 elad661 force-pushed the alert_hash_expression branch from cbe1aa7 to 07336f3 Compare December 6, 2017 19:29
@elad661 elad661 force-pushed the alert_hash_expression branch from 07336f3 to 5715db3 Compare December 6, 2017 19:32
@miq-bot
Copy link
Member

miq-bot commented Dec 6, 2017

Checked commit zakiva@5715db3 with ruby 2.3.3, rubocop 0.47.1, haml-lint 0.20.0, and yamllint 1.10.0
2 files checked, 0 offenses detected
Everything looks fine. 🍰

@@ -71,7 +71,7 @@
sample_alert_definition = {
"description" => "Test Alert Definition",
"db" => "ContainerNode",
"expression" => { "eval_method" => "dwh_generic", "mode" => "internal", "options" => {} },
"expression" => {"exp" => {"=" => {"field" => "Vm-name", "value" => "foo"}}, "context_type" => nil},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious about these changes, description mentioned being backward compatible, why are these needed ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because, as @imtayadeway noted in this comment #76 (comment), the expression there was not actually a MiqExpression, so that means the test was wrong. It wasn't supposed to "work" like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks for clarifying.

@abellotti abellotti added this to the Sprint 76 Ending Jan 1, 2018 milestone Dec 19, 2017
@abellotti
Copy link
Member

Thanks @zakiva for the API Enhancement and everyone for the reviews. This LGTM!! 👍

@abellotti abellotti merged commit 339ce22 into ManageIQ:master Dec 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants