HewlettPackard / oneview-sdk-ruby

This project is no longer being developed and has limited support. In the near future this repository will be fully deprecated. Please consider using other OneView projects, such as Golang and Python.
Apache License 2.0
12 stars 16 forks source link

I3S - Simplify login to i3s through oneview client #166

Closed marikrg closed 7 years ago

marikrg commented 7 years ago

Scenario/Intent

As far as I can see, currently I'm able to authenticate on Image Streamer only using a token I have tried to access with an username/password and I got the error: Must set token option

From the tools point of view, may be better for some of them authenticate with username/password. The token expires frequently and the tools doesn't provide (and I believe it shouldn't) a way to generate it.

Then, it shouldn't be supported by ImageStreamer client?

Environment Details

Steps to Reproduce

  1. Define username/password instead of token
  2. Instantiate the ImageStreamer Client.
  3. Error Must set token option is raised.

Expected Result

Possible solution:

  1. Define username/password instead of a token
  2. Instantiate the ImageStreamer Client.
  3. Authenticate against OneView appliance and uses the generated token for ImageStreamer rest calls (as a single sign-on).

Actual Result

[What actually happens after the steps above? Include error output or a link to a gist.]

jsmartt commented 7 years ago

@marikrg , I think the reason this was done is because I3S doesn't have an endpoint to login itself; it just uses the token provided from OneView. So you'd have to create a OneView client, then copy that into an I3S client. It seems like it could make sense to roll that all into 1, but then the client would hold 2 separate URLs (1 for OV & 1 for I3S). That seems strange/wrong to implement to me, so although it's a little strange, I think the current solution is the best way (as long as I3S doesn't have a login endpoint itself). @aalexmonteiro , do you have anything to add/correct about that?

aalexmonteiro commented 7 years ago

@jsmartt , exactly. The #167, which was done by you, client attributes can be updated and the token can be set again when it expires. Looks good for me.

fgbulsoni commented 7 years ago

@jsmartt @aalexmonteiro So, if I'm running many operations, like one of our devops tools could, our expected design is to always assume my token has expired and invoke the refresh before any operation? Because I'm guessing it would be expiring constantly and we'd have no way to actually be sure if it is working.

aalexmonteiro commented 7 years ago

@fgbulsoni , It's strange the token is expiring so fast. @jsmartt , has implemented a refresh_login here on client.rb, which updates the token. I think you can use this method to get the new token before each operation and set the new token on the i3s client. I can't think of another solution at the moment.

jsmartt commented 7 years ago

I've never had an issue with it, even after pretty long runs using a single token. It looks like by default, tokens expire 24-hrs after creation. It looks like a session can be extended as well, but I kept getting 401 unauthorized responses after trying to extend my own session. I'm sure we can figure something out if need be, but I think until OneView allows persistent tokens, most users will use a username/password to log in at the beginning of the execution and never run into issues.

One possible solution I thought of would be to keep track of when the session was created & how long it would be valid, so that any time a rest call was made after it is expected to expire, we can automatically refresh the token. But again, I don't see it as a problem that needs to be solved at this point

fgbulsoni commented 7 years ago

@marikrg Would you mind sharing how this was implemented on the Python SDK, for comparison purposes? Are we supporting both the usage of tokens and username/password or just username/password there? I'm trying to see if it would be worth putting any effort into having both of these ways implemented vs what we have now. Do you expect to run into issues with what @jsmartt has just mentioned?

marikrg commented 7 years ago

The Python SDK authentication is based on the OneView / Image Streamer UI:

  1. Login into OneView.
  2. If you log in successfully, you will be able to access Image Streamer tool.

In the Python SDK, first of all, the user needs to create the OneViewClient:

oneview_client = OneViewClient.from_json_file(EXAMPLE_CONFIG_FILE)

You can check an example of json config file at: https://github.com/HewlettPackard/python-hpOneView/blob/master/examples/config-rename.json

Then, to create the ImageStreamerClient, you must call the create_image_streamer_client method from the previously created OneViewClient instance.

image_streamer_client = oneview_client.create_image_streamer_client()
marikrg commented 7 years ago

In addition, in the Python SDK the user can provide either an username/password or a token.

jsmartt commented 7 years ago

I like the create_image_streamer_client method. Perhaps that's something we can add.

fgbulsoni commented 7 years ago

I like the create_image_streamer_client method.

I like it as well, and another plus side of this approach is that since it acts similarly to the way the GUI handles the authentication, it might be the way a customer would expect it to be done.

aalexmonteiro commented 7 years ago

The way it was done in python is interesting. But I have some thoughts on this:

1-In Ruby we will have to remove the inheritance between oneview client and i3s client and use composition, this will imply code duplication. In python it has a bit of replication (I do not see problems), But in the case of ruby, this replication would be slightly larger.

2-Although interesting, somehow we have two instance variables for each type of client.

Current code.

client = client = OneviewSDK::Client.new(params)
client_i3s = OneviewSDK::ImageStreamer::Client.new(client.token)

Proposed code

client = OneviewSDK::Client.new
client_i3s = client.create_i3s_client

I do not see any great advantage to change, since we will lose code reuse. That is my opinion, but that does not mean that I am right.

tmiotto commented 7 years ago

@aalexmonteiro

The idea is to make this token not visible to the end user, like in the proposed code.

I cannot see why it will be necessary to remove the inheritance, and if removing would cause code duplication.

Also, it would be easier for the tools to have a creation method passing both data in the params: e.g.

def initialize(options = {})
  @token = if options[:oneview_client]
             @oneview_client = OneviewSDK::Client.new(options[:oneview_client])
             @oneview_client.token
           else
             options[:token] || ENV['I3S_TOKEN']
  [...]
end

Also, I noticed the initialize in the ImageStreamer::Client is almost completely duplicated, we have a lot of room for improvements there.

aalexmonteiro commented 7 years ago

@tmiotto , I got it. Now that seems clear for me.

ricardogpsf commented 7 years ago

I agree with @tmiotto about to pass data for initializemethod of ImageStreamer::Client. And more, I think so the options[:token] is unecessary and just options[:oneview_client] is necessary (or just an client instance of OneviewSDK::Client). The end user should not know about token or about passing token.

jsmartt commented 7 years ago

I think it's still good for the I3S client initialize method to have all the options that it has now, but it would be nice for the OV client to have the ov_client.new_i3s_client method to abstract those options where they are not needed. That way we can still have the flexibility of all the options, but the simplicity of a single method call. Plus, it's less typing than OneviewSDK::ImageStreamer::Client.new(oneview_client: ov_client). I can see the method in discussion looking something like:

def new_i3s_client(opts = {})
  OneviewSDK::ImageStreamer::Client.new(opts.merge(token: @token))
end

And usage:

i3s_client = ov_client.new_i3s_client
# or
i3s_client = ov_client.new_i3s_client(api_ver: 300)

Simple, yet flexible

tmiotto commented 7 years ago

Yeah... this is a resonable solution, in the code it would look like:

@ov_client = OneviewSDK::Client.new(
  url: 'https://xxx.xxx.xxx.xxx',
  user: 'Admin',
  password: 'admin',
  ssl_enabled: true,
  api_version: 300
)

@i3s_client = @ov_client.new_i3s_client(
  url: 'https://yyy.yyy.yyy.yyy',
  ssl_enabled: true,
  api_version: 300
)
fgbulsoni commented 7 years ago

Changing tittle since it seems we've all agreed it would be good/important to have this feature implemented.