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

Create a hawkular client for partial endpoints #13814

Merged
merged 1 commit into from
Feb 14, 2017

Conversation

moolitayer
Copy link

@moolitayer moolitayer commented Feb 8, 2017

Creating the aggregated client does a health check against all endpoints.
The Hawkular deployed in OpenShift does not include some of those endpoints (e.g inventory)

Extracted from #12773

@moolitayer
Copy link
Author

@miq-bot add_label providers/containers, bug

@moolitayer
Copy link
Author

@lucasponce @cben @simon3z could you please review?

@lucasponce
Copy link
Contributor

@josejulio @cfcosta Please could you also review this PR ?
@moolitayer is using the hawkular ruby client for the datawarehouse provider and he commented some potential problems with the validation of non existing endpoints in the backend.
OpenShift deployments only uses Metrics/Alerting endpoint.

Copy link

@cfcosta cfcosta left a comment

Choose a reason for hiding this comment

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

Just some clarifications and some small questions.

port,
authentication_token('default'),
options[:alerts])
@clients ||= {}
Copy link

Choose a reason for hiding this comment

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

Where is the @client being defined then? Is it utilized anywhere else in the class?

Copy link
Member

Choose a reason for hiding this comment

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

I think they use the returned variable and only use @client and the new @clients to save the connection for future calls.
Is that right @moolitayer ?

Copy link
Author

Choose a reason for hiding this comment

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

Right, this is a memoization technique @client is defined only here. On the first call it will create and return the relevant client and from that point on it will return the stored value.

authentication_token('default'),
options[:alerts])
@clients ||= {}
@clients[options[:alerts] ? :alerts : :metrics] ||= self.class.raw_connect(
Copy link

Choose a reason for hiding this comment

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

I don't like this options[:alerts] ? :alerts : :metrics inside the hash accessor, can you extract it into a variable? Would make a lot more sense.

Copy link
Author

Choose a reason for hiding this comment

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

Honestly I'm not a fan of creating variables that are not used.
(not a performance thing, only readability)

If you feel current code is not readable I suggest:

@clients ||= {}
@clients[
  options[:alerts] ? :alerts : :metrics
] ||= self.class.raw_connect(
  hostname,
  port,
  authentication_token('default'),
  options[:alerts]
)

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I think the new alternative is more readable. I was also confused with the old one. WDYT @cfcosta ?

Copy link
Author

Choose a reason for hiding this comment

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

changed this for now

Copy link
Contributor

Choose a reason for hiding this comment

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

The variable would be used, just very locally:

client_type = options[:alerts] ? :alerts : :metrics
@clients[client_type] || = ...

But that highlights the fact there are approximately four representations of client type around here:

  1. ::Hawkular::Alerts::AlertsClient vs ::Hawkular::Metrics::Client
  2. :alerts vs :metrics here
  3. raw_connect takes positional boolean [3.5. connect takes named boolean]
    Q: can any more endpoints become needed later? E.g. Openshift manager fetch_hawk_inv() creates aggregated client then does .strings.get_data().
  4. calling alerts_client (and maybe metrics_client) instead of connect.

Is it feasible to change raw_connect and connect interface to take symbol (:alerts vs :metrics) instead of booleans?
Then you have less representations, can do simpler options[:type] || :metrics, and might not need {alerts,metrics}_client aliases...

Copy link
Author

Choose a reason for hiding this comment

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

There are only two options so It makes more sense to use a boolean instead of a string with two options (openshift cluster only exposes alerts and metrics, string is a type of metric)

Raw connect and connect have their reasons to exists - interfaces calling connect use a context
and raw_connect requires intimate knowledge of provider parameters. If you take a look at other providers they all look exactly the same. container_provider_mixin does something a little different: it passes the options forward, but I see no point in passing some of the parameters as options and some positionals.

@moolitayer
Copy link
Author

@cfcosta Thanks for the review
@cfcosta @josejulio please take a look

)
end

def alerts_client
Copy link
Member

Choose a reason for hiding this comment

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

Might also be worth to create a metrics_client while you are at it.

Copy link
Author

Choose a reason for hiding this comment

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

Sure why not

Copy link

@cfcosta cfcosta left a comment

Choose a reason for hiding this comment

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

LGTM

@moolitayer
Copy link
Author

moolitayer commented Feb 9, 2017

@cben @josejulio Please review

@cben
Copy link
Contributor

cben commented Feb 9, 2017

I still dislike the boolean because "alerts false" here doesn't mean "the opposite or alerts" or "don't fetch alerts", it means "the other unrelated thing out of two"...

LGTM on the PR, it fixes a bug and the code is no worse than before.

P.S. if the options hash grows any new parameters in the future, it's easy for the bug to re-appear.
In theory the Right Thing is memoizing on @clients[canonicalized_options.freeze] where canonicalized_options currently means ensuring :alerts is either true or false. Not worth the complexity I suppose, if you think that's a concern just add a comment.

P.P.S. AFAIK a Hawkular::Client currently constructs new RestClient for every request, and a RestClient currently opens new http connection for every request. So the memoization is not really helping(?), just dropping it is also an option.

@cfcosta
Copy link

cfcosta commented Feb 9, 2017

@cben +1 to dropping memoization, on a stylistic level it makes the code weird to read, and as you said it does not exactly do anything.

In case we're doing lots of that kind of memoization, we can start using Memoist, which is a nice, elegant way of doing memoization.

And as of Ruby 2.1 (I guess) something like this is possible:

memoize def foo; end

And it would be equivalent to do:

def foo; end
memoize :foo

@moolitayer
Copy link
Author

moolitayer commented Feb 9, 2017

Outside the scope of this pr:

  • changing memoization mechanism
  • should we do memoization?

I have my thoughts about both of these, but they are both out of scope of this PR

in the scope of this pr:

  • should we pass a boolean or symbol to self.raw_connect if I understand correctly

They both have good reasons, the difference as I see it is;

  • option a
class.raw_connect(..., false) # or nil or nothing
class.raw_connect(..., true)


def self.raw_connect(..., alerts=false)
  client_klass = alerts ? ...
  ...
  • option b
class.raw_connect(..., :alerts)
class.raw_connect(..., :events)


def self.raw_connect(..., type=:alerts)
  client_klass = case type
    when :alerts
      ...
    when :event
      ..
    else
      ..

I prefer option a here

@moolitayer
Copy link
Author

@cben @cfcosta @josejulio please review

@cfcosta
Copy link

cfcosta commented Feb 9, 2017

I prefer option b, because its not default behaviour OR other behaviour, but two different kinds of behaviour being switched by a flag. Going even further, I prefer something like this:

def self.client_for(type)
  case type
  when :metrics
   ::Hawkular::Metrics::Client
  when :alerts
    ::Hawkular::Alerts::AlertsClient
  else
    raise ArgumentError, "Client not found for #{type}"
  end
end

def self.raw_connect(..., type = :alerts)
  client_for(type).new(...)
end

@moolitayer
Copy link
Author

moolitayer commented Feb 9, 2017

@cben @josejulio are you happy with @cfcosta's suggestion?

@moolitayer
Copy link
Author

@cben @josejulio sorry for nagging but I have a ton of other stuff dependant on this

@josejulio
Copy link
Member

@cfcosta suggestion is good

@josejulio
Copy link
Member

P.P.S. AFAIK a Hawkular::Client currently constructs new RestClient for every request, and a RestClient currently opens new http connection for every request. So the memoization is not really helping(?), just dropping it is also an option.

That's right, but some clients (e.g. metrics) do check the version to know if use legacy API or not.
alerts doesn't do that.

I guess the whole Hawkular client could benefit from checking it lazily (I suppose this PR wouldn't exist if it was like that).

@moolitayer
Copy link
Author

Thanks @josejulio
@cben are you fine with that?

@cben
Copy link
Contributor

cben commented Feb 9, 2017

Love @cfcosta's suggestion.

@moolitayer
Copy link
Author

@cben @josejulio @cfcosta please review

@josejulio
Copy link
Member

LGTM

@moolitayer moolitayer changed the title Change hawkular client usage to work with partial endpoints Create a hawkular client for partial endpoints Feb 9, 2017
@@ -35,21 +35,42 @@ def self.verify_ssl_mode
OpenSSL::SSL::VERIFY_NONE
end

def self.client_for(type)
Copy link
Contributor

@simon3z simon3z Feb 10, 2017

Choose a reason for hiding this comment

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

@moolitayer why don't you keep this in raw_connect?

@simon3z
Copy link
Contributor

simon3z commented Feb 10, 2017

@miq-bot assign moolitayer

@moolitayer
Copy link
Author

@miq-bot assign @simon3z
please review

@miq-bot miq-bot assigned simon3z and unassigned moolitayer Feb 12, 2017
@moolitayer
Copy link
Author

@simon3z can we merge this?

port,
authentication_token('default'),
options[:alerts])
raise ArgumentError, "Client not found for #{type}" unless CLIENT_KLASSES.keys.include?(options[:type])
Copy link
Contributor

Choose a reason for hiding this comment

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

@moolitayer why do you need this? don't you get it for free from raw_connect?

Copy link
Author

Choose a reason for hiding this comment

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

I was worried about the assignment to @client[:something_unexpected] but I guess such assignment would never happen.
Removing

Copy link

Choose a reason for hiding this comment

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

Also not necessary to do the check twice.

@@ -11,6 +11,11 @@ class Hawkular::DatawarehouseManager < ManageIQ::Providers::DatawarehouseManager
DEFAULT_PORT = 80
default_value_for :port, DEFAULT_PORT

CLIENT_KLASSES = {
Copy link

Choose a reason for hiding this comment

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

This forces the constants to be autoloaded before the class itself is loaded, so I'd advise against it. The later we manage to move constant loading the better.

client = alerts ? ::Hawkular::Alerts::AlertsClient : ::Hawkular::Metrics::Client
client.new(
def self.raw_connect(hostname, port, token, type = :alerts)
raise ArgumentError, "Client not found for #{type}" unless CLIENT_KLASSES.keys.include?(type)
Copy link

Choose a reason for hiding this comment

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

unless CLIENT_CLASSES[type]

Also, no need to name something klass unless it conflicts with a keyword.

port,
authentication_token('default'),
options[:alerts])
raise ArgumentError, "Client not found for #{type}" unless CLIENT_KLASSES.keys.include?(options[:type])
Copy link

Choose a reason for hiding this comment

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

Also not necessary to do the check twice.

)
end

def alerts_client
Copy link

Choose a reason for hiding this comment

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

Since you are already doing that, why not do it with metaprogramming? That way you don't need to add another method in case another client is added.

CLIENT_CLASSES.keys.each do |type|
  define_method("#{type}_client") do
    connect(:type => type)
  end
end

@miq-bot
Copy link
Member

miq-bot commented Feb 13, 2017

Checked commit moolitayer@aeb4035 with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
1 file checked, 0 offenses detected
Everything looks good. ⭐

@moolitayer
Copy link
Author

@simon3z please review

@simon3z
Copy link
Contributor

simon3z commented Feb 13, 2017

LGTM 👍
cc @chessbyte

@miq-bot assign blomquisg

@miq-bot miq-bot assigned blomquisg and unassigned simon3z Feb 13, 2017
@blomquisg blomquisg merged commit 4242a1e into ManageIQ:master Feb 14, 2017
@blomquisg blomquisg added this to the Sprint 55 Ending Feb 27, 2017 milestone Feb 14, 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