-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
CLOUDSTACK-10103: Cloudian Connector for CloudStack #2284
CLOUDSTACK-10103: Cloudian Connector for CloudStack #2284
Conversation
95e7fad
to
a075c6d
Compare
Code review requested @nvazquez @DaanHoogland @borisstoyanov /cc @PaulAngus @dagsonstebo |
@blueorangutan package |
@borisstoyanov a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress. |
Packaging result: ✔centos6 ✔centos7 ✔debian. JID-1131 |
@blueorangutan test |
@rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests |
Trillian test result (tid-1559)
|
Test LGTM, no regressions seen. Errors are known intermittent failures. |
<dependency> | ||
<groupId>com.github.tomakehurst</groupId> | ||
<artifactId>wiremock</artifactId> | ||
<version>2.8.0</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we store version in a pom property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll fix this.
final CloudianClient client = getClient(); | ||
if (existingUser != null) { | ||
final User accountUser = userDao.listByAccount(account.getId()).get(0); | ||
final String fullName = String.format("%s %s (%s)", accountUser.getFirstname(), accountUser.getLastname(), account.getAccountName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we delegate full name creation to a new method which takes an User
as parameter to avoid code duplicates on full name creation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, however, we're using the fullName
prior to comparison and we're only updating if there is any mismatch is fields, so we need to keep it here.
|
||
public List<CloudianUser> listUsers(final String groupId) { | ||
if (Strings.isNullOrEmpty(groupId)) { | ||
return new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason for returning an empty list instead of null for this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we would want to avoid NPEs, which is why we're returning empty arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rhtyd great work! I have left some few comments, other than that, code LGTM
Community docs submitted: apache/cloudstack-docs#21 |
a075c6d
to
029eaaf
Compare
Thanks @nvazquez I've addressed the issues you've mentioned now. |
Pinging for review - @DaanHoogland @karuturi @wido @GabrielBrascher @rafaelweingartner @ustcweizhou and others |
Code LGTM. To bad we cannot have integration tests. Let's hope Cloudian will support it (with infra ;) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rhtyd I have seen some points that might benefit from improvements, what do you think?
//////////////// Public APIs: Group ///////////////////// | ||
///////////////////////////////////////////////////////// | ||
|
||
public boolean addGroup(final CloudianGroup group) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about throwing a runtime exception (a custom one) if it is not possible to add this group
object?
It feels more natural for a method to throw an exception if it fails than to return a Boolean indicating its status.
The same applies for the remove
, list
(instead of returning null?) and update
methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime exceptions are suitably thrown by checkResponseTimeOut and other methods. The boolean value may be used to ascertain whether the operation was successful or not. Throwing and catching exceptions will ultimately be similar pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the result of a code that throws an exception and then catching it is the same.
I normally like the idea of reading the signature of the method and understanding what it does, that is why I care so much about naming and documentation stuff sometimes. Having said that, I like to divide methods into two groups, the ones that do something and the ones that return something; this makes things a little more clear when coding, and normally help me divide better the tasks I am coding. So, when I read methods such as save
, remove
, update
and so on, they are in the imperative form, ordering “the system” to do something; those method signatures do not give me a hint regarding the result of the operation, and as such I would expect an exception if things go south. On the other hand, when I see methods get
, retrieve
, count
and so on, they are asking for something, so I expect something to be returned.
I see the use of this style having methods that do something and return the state of the operation in C programs. The difference is that in C people normally use numbers as status results of methods, and then we have a broader range of different possible status to be informed to the piece of code that is using the method.
This point that I am touching relates to semantics (conventions). I have seen this standard in some part of our code base and I do not find them very intuitive (maybe a Javadoc would help?).
For instance, the addGroup
method, it will return false
if the group is null; it also returns false when
response.getStatusLine().getStatusCode()is not the expected one; moreover, it returns false when the method
checkResponseTimeOutdoes not re-throws an exception (when this solves to false
(e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException)`. This means that I cannot trust the result of the method (the true or false to indicate if everything is working there) if I want to properly deal errors and exceptions.
It is not that I am against the code the way it is; I only wanted to call attention to a point that can help us to reduce the barrier for newcomers. The more readable and understandable a code is at first sight, the easier it is for newcomers to start working with it.
P.S. sorry for the long text, I also believe the simple and small java docs can help making the methods easier to understand without needing to read code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rafaelweingartner I disagree with several of your remarks, there is no guidelines or rule that would require everyone to throw an exception. Exception would mean something happened out of the ordinary against set rules/contracts, and per Cloudian API docs some APIs may return 204
(no content) to return a resource that does not exist or failed. A timeout is a valid exception, however failing to add a group or a user is expected (for example, the group already existed etc). The implementation of the Cloudian API client is driven by its uses per the feature, and currently, we only need to know whether a user/group is not synced and should be added.
The client implementation is not for public consumption as a client/library but restricted to the plugin to limit its scope, pragmatic implementation, and use. For general use, there exists Cloudian java sdks that can be used, however, including them with CloudStack release had licensing and other issues which is why a minimal client was implemented to cater to the limited feature scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that there is no guideline that is why I touched this matter. My point regarding exceptions was that, if I read the method name when using it, I have no idea why it returns a boolean, I will have to check the implementation, which I do not find a good way of working, if we force everybody that is doing something in our code base to always check every single method they use because of a lack of documentation of lack of clarity in method naming.
Also, if the method returns false
for instance, we just know that something is not successful, but we do not know what (group already there, some other exception, and so on).
As I said, I am not against it. I understand that these are personal ways someone designs a solution, and that is fair. I only wanted to show these points and try to understand why we are going towards one direction and not the other.
try { | ||
final HttpResponse response = get(String.format("/group?groupId=%s", groupId)); | ||
checkResponseOK(response); | ||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about putting the boolean condition response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT || response.getEntity() == null || response.getEntity().getContent() == null
in a method? it repeats quite often. Then, it is even possible to write a unit test for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll refactor it.
Several organizations use Cloudian as S3 provider, this implements the Cloudian Management Console connector for CloudStack that can do the following: - Provide ease in connector configuration using CloudStack global settings - Perform SSO from CloudStack UI into Cloudian Management Console (CMC) when the connector is enabled - Automatic provisioning and de-provisioning of CloudStack accounts and domains as Cloudian users and groups respectively - During CloudStack UI logout, logout user from CMC - CloudStack account will be mapped to Cloudian Users, and CloudStack domain will be mapped to Cloudian Groups. - The CloudStack admin account is mapped to Cloudian admin (user name configurable). - The user/group provisioning will be from CloudStack to Cloudian only, i.e. user/group addition/removal/updation/deactivation in Cloudian portal (CMC) won't propagate the changes to CloudStack. FS: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Cloudian+Connector+for+CloudStack New APIs: - `cloudianIsEnabled`: API to check whether Cloudian Connector is enabled. - `cloudianSsoLogin`: Performs SSO for the logged-in, requesting user and returns the URL that can be used to perform SSO and log into CMC. New Global Settings: - cloudian.connector.enabled (false) If set to true, this enables the Cloudian Connector for CloudStack. Restarting management server(s) is required. - cloudian.admin.host (s3-admin.cloudian.com) The host where Cloudian Admin services are accessible. - cloudian.admin.port (19443) The admin service port. - cloudian.admin.protocol (https) The admin service API scheme/protocol. - cloudian.validate.ssl (true) When set to true, this validates the certificate of the https-enabled admin API service. - cloudian.admin.user (sysadmin) The admin user's name when making (admin) API calls. - cloudian.admin.password (public) The admin password used when making (admin) API calls. - cloudian.api.request.timeout (5) The API request timeout in seconds used by the internal HTTP/s client. - cloudian.cmc.admin.user (admin) The CMC admin user's name. - cloudian.cmc.host (cmc.cloudian.com) The CMC host. - cloudian.cmc.port (8443) The CMC service port. - cloudian.cmc.protocol (https) The CMC service scheme/protocol. - cloudian.sso.key (ss0sh5r3dk3y) The Single-Sign-On shared key. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
029eaaf
to
b618739
Compare
* Generates single-sign on URL for logged in user | ||
* @return returns the SSO URL string | ||
*/ | ||
String generateSsoUrl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rafaelweingartner FYI, we've java docs for this interface that we may be consumed by other parts of the codebase. The Cloudian client does not have them because we don't have it to be generally used by other parts of the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did not see if there was an interface, my bad...
No regressions are seen, no outstanding issues. |
@@ -0,0 +1,18 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rhtyd, As there is no content in this file, Can we remove this or do you use it for any other purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sake of completion, all plugins have this file and was put for the cloudian
UI plugin. require.js
tries to GET
this file, when this is missing it causes a HTTP 404, however, we'll need to test if it causes any rendering/load issue in the UI. You may remove this after performing this test.
Several organizations use Cloudian as S3 provider, this implements the
Cloudian Management Console connector for CloudStack that can do the
following:
settings
when the connector is enabled
domains as Cloudian users and groups respectively
domain will be mapped to Cloudian Groups.
configurable).
i.e. user/group addition/removal/updation/deactivation in Cloudian
portal (CMC) won't propagate the changes to CloudStack.
FS: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Cloudian+Connector+for+CloudStack
New APIs:
cloudianIsEnabled
: API to check whether Cloudian Connector is enabled.cloudianSsoLogin
: Performs SSO for the logged-in, requesting userand returns the URL that can be used to perform
SSO and log into CMC.
New Global Settings:
If set to true, this enables the Cloudian Connector for CloudStack.
Restarting management server(s) is required.
The host where Cloudian Admin services are accessible.
The admin service port.
The admin service API scheme/protocol.
When set to true, this validates the certificate of the https-enabled
admin API service.
The admin user's name when making (admin) API calls.
The admin password used when making (admin) API calls.
The API request timeout in seconds used by the internal HTTP/s client.
The CMC admin user's name.
The CMC host.
The CMC service port.
The CMC service scheme/protocol.
The Single-Sign-On shared key.