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

First draft of a simple auth-aware HttpClient service #40

Merged
merged 5 commits into from
May 25, 2017

Conversation

ajs6f
Copy link
Contributor

@ajs6f ajs6f commented May 3, 2017

NOT FOR IMMEDIATE MERGE

Add API-X to vagrant install: (Islandora/documentation#504)

  • Other Relevant Links (Google Groups discussion, related pull requests, Release pull requests, etc.)

fcrepo4-labs/fcrepo-api-x#112

What does this Pull Request do?

This PR adds an HttpClient to the OSGi service registry that uses a configurable but static token to try to authenticate to its server. The intended "user" of this service is API-X, which needs to be able to act with authentication against a Syn-protected Fedora repository to install its own configuration.

What's new?

There is now an authenticating client as described above available in the OSGi service registry.

How should this be tested?

This PR will only be useful once API-X is modified to accept an authenticating client for use setting up its configuration. @birkland is willing to work on that problem.

Interested parties

@dhlamb @birkland @jonathangreen @whikloj

@codecov
Copy link

codecov bot commented May 3, 2017

Codecov Report

Merging #40 into master will increase coverage by 13.33%.
The diff coverage is 91.66%.

Impacted file tree graph

@@              Coverage Diff              @@
##             master      #40       +/-   ##
=============================================
+ Coverage     71.18%   84.52%   +13.33%     
- Complexity        3       35       +32     
=============================================
  Files             2        7        +5     
  Lines            59      252      +193     
  Branches          0        1        +1     
=============================================
+ Hits             42      213      +171     
- Misses           17       38       +21     
- Partials          0        1        +1
Impacted Files Coverage Δ Complexity Δ
...aca/http/client/StaticTokenRequestInterceptor.java 91.66% <91.66%> (ø) 6 <6> (?)
.../ca/islandora/alpaca/indexing/fcrepo/AS2Event.java 84.61% <0%> (ø) 7% <0%> (?)
.../ca/islandora/alpaca/indexing/fcrepo/AS2Actor.java 71.42% <0%> (ø) 3% <0%> (?)
...slandora/alpaca/indexing/fcrepo/FcrepoIndexer.java 96.7% <0%> (ø) 3% <0%> (?)
...dora/alpaca/indexing/fcrepo/FcrepoIndexerBean.java 80% <0%> (ø) 13% <0%> (?)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 82c487d...4b5ce97. Read the comment docs.

Copy link
Contributor

@dannylamb dannylamb left a comment

Choose a reason for hiding this comment

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

This looks good @ajs6f, but there's a slew of checkstyle violations in the Travis output. Can you take care of those?

And more importantly, why isn't Travis failing when it hits those?


instruction 'Import-Package', 'org.apache.http.client,' +
defaultOsgiImports
instruction 'Export-Package', 'ca.islandora.indexing.http.client'
Copy link
Contributor

Choose a reason for hiding this comment

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

s/indexing/alpaca

@birkland
Copy link

birkland commented May 3, 2017

API-X PR is here:
fcrepo4-labs/fcrepo-api-x#113

Once it's merged, adding to org.fcrepo.apix.jena.cfg the property httpclient.osgi.jndi.service.name=claw/httpClient should do the trick

</cm:default-properties>
</cm:property-placeholder>

<service id="httpClient" interface="org.apache.http.client.HttpClient" filter="(osgi.jndi.service.name=claw/httpClient)">
Copy link

Choose a reason for hiding this comment

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

Does this work? I've never tried the filter property when exporting a service. My only experience is with the form:

<service-properties>
  <entry key="osgi.jndi.service.name" value="claw/httpClient" />
</service-properties>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably I was just being absent minded. This is why we need more i-tests... I will fix this to the style you are suggesting, which I'm sure is correct.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 6, 2017

@birkland, I've got your PR and my PR together in a claw_vagrant, and the bundles for API-X do load and start:

301 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-binding
302 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-jena
303 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-listener
304 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-loader
305 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-model
306 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-registry
307 | Active |  80 | 0.2.0.SNAPSHOT     | fcrepo-api-x-routing

but I'm getting a jillion 401s off of API-X after startup of the form I record here. Eventually, the logs settle down and just repeat a pattern like this:

2017-05-06 17:14:54,522 | INFO  | pool-36-thread-4 | LookupOntologyRegistry           | 302 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Indexing ontologies...
2017-05-06 17:14:54,525 | WARN  | pool-36-thread-4 | HttpRegistry                     | 306 - fcrepo-api-x-registry - 0.2.0.SNAPSHOT | 401
2017-05-06 17:14:54,525 | WARN  | pool-36-thread-5 | HttpRegistry                     | 306 - fcrepo-api-x-registry - 0.2.0.SNAPSHOT | 401
2017-05-06 17:14:54,992 | WARN  | pool-36-thread-1 | HttpRegistry                     | 306 - fcrepo-api-x-registry - 0.2.0.SNAPSHOT | 401
2017-05-06 17:14:55,527 | INFO  | pool-36-thread-4 | LookupOntologyRegistry           | 302 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Indexing ontologies...

apparently, forever.

Tentatively, I'm thinking that this is due to actual Camel pipelines (looks like in fcrepo-api-x-loader?) not being equipped with the same authenticating client as the startup code?

Do you buy that theory? Can I give you further diagnostic info? This is far enough into the guts of API-X that I am not very confident in my diagnostic abilities.

@birkland
Copy link

birkland commented May 8, 2017

I think I found the issue, it's not with fcrepo-api-x-loader, but is with the LdpContainerRegistry. There's another place that needs the special client, but is getting the default one instead. I think I overlooked that. Fixing now...

@birkland
Copy link

birkland commented May 8, 2017

@ajs6f OK, just pushed an update. There's a little configuration change. Instead of putting the property httpclient.osgi.jndi.service.name in org.fcrepo.apix.jena.cfg, put it in org.fcrepo.apix.registry.http.cfg.

Clearly, we need to incorporate auth into the ITs on the API-X side. I've created a new issue to do that separately, so we can just get the fix merged right away for CLAW

@ajs6f
Copy link
Contributor Author

ajs6f commented May 8, 2017

@birkland, unfortunately, I think something is still missing.

I pulled your last commit and rebuilt API-X. Then I altered the config as you specified above and did the usual feature:install fcrepo-api-x. Sadly, I continue to get the same

java.lang.RuntimeException: Error reading from http://localhost:8080/fcrepo/rest/apix/extensions
	at org.fcrepo.apix.jena.impl.LdpContainerRegistry.list(LdpContainerRegistry.java:261)

It seems that to get to that point, the authenticating client must be available to API-X (for a successful startup), so is it possible that there is some other point at which the client may need to be injected...? Seems unlikely, but I'm grasping at straws. :)

@birkland
Copy link

birkland commented May 8, 2017

Darn it, that's why we need ITs for this. I can never get the OSGi wiring right on the first try; was injecting the wrong client. Now it should be using the right one.

I'm presently working on getting the existing ITs set up to cope with auth.

@birkland
Copy link

birkland commented May 8, 2017

@ajs6f I just pushed an update

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

@birkland Thanks-- having applied the update (to the blueprint.xml) I am now getting full bundle activation, lots of JMS connection errors (which don't bother me and seem totally unrelated), and then a log with endless repeating:

2017-05-09 15:08:23,490 | INFO  | pool-30-thread-1 | LdpContainerRegistry             | 66 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Looking for container http://localhost:8080/fcrepo/rest/apix/extensions
2017-05-09 15:08:23,490 | INFO  | pool-30-thread-2 | LdpContainerRegistry             | 66 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Looking for container http://localhost:8080/fcrepo/rest/apix/ontologies
2017-05-09 15:08:23,490 | INFO  | pool-30-thread-3 | LdpContainerRegistry             | 66 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Looking for container http://localhost:8080/fcrepo/rest/apix/services
2017-05-09 15:08:24,492 | INFO  | pool-30-thread-3 | LdpContainerRegistry             | 66 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Looking for container http://localhost:8080/fcrepo/rest/apix/services
2017-05-09 15:08:24,492 | INFO  | pool-30-thread-2 | LdpContainerRegistry             | 66 - fcrepo-api-x-jena - 0.2.0.SNAPSHOT | Looking for container http://localhost:8080/fcrepo/rest/apix/ontologies

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

Interestingly (or maybe not) when I go to feature:uninstall fcrepo-api-x I get

2017-05-09 15:11:08,626 | INFO  | 3 - ShutdownTask | DefaultShutdownStrategy          | 89 - org.apache.camel.camel-core - 2.18.1 | Waiting as there are still 4 inflight and pending exchanges to complete, timeout in 256 seconds. Inflights per route: [load-service-uri = 1, load-prepare = 1, loader-http = 1, load-extension = 1]
2017-05-09 15:11:08,627 | DEBUG | 3 - ShutdownTask | DefaultShutdownStrategy          | 89 - org.apache.camel.camel-core - 2.18.1 | There are 2 inflight exchanges:
	InflightExchange: [exchangeId=ID-claw-37933-1494342459440-2-2, fromRouteId=load-extension, routeId=load-extension, nodeId=to20, elapsed=195911, duration=195926]
	InflightExchange: [exchangeId=ID-claw-37933-1494342459440-2-3, fromRouteId=loader-http, routeId=load-service-uri, nodeId=process9, elapsed=195706, duration=195779]

@birkland
Copy link

birkland commented May 9, 2017

@ajs6f Hm, you do not see messages like "Container ___ does not exist, adding"?

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

@birkland No, exactly. That's odd, no? Here is a full log. The sequence was:

  1. Start api-x features.
  2. fcrepo-api-x-registry goes into failure, other bundles go either into grace period or start status.
  3. I restart the fcrepo-api-x-registry bundle. You can quickly find that point in the gist'd log by finding the phrase "RESTART fcrepo-api-x-registry" which I inserted into the log.
  4. All bundles start, and the log completes as shown.

@birkland
Copy link

birkland commented May 9, 2017

@ajs6f What must be happening is that a HEAD request fails, and the underlying cause isn't being logged (or is being logged at a lower level). In any case, it's not being user friendly, which bugs me. I don't want to waste too much of your time doing this blind. Let me see if I can finish getting the auth-enabled ITs working in order to observe firsthand...

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

Oh, I finally see the root problem for why fcrepo-api-x-registry failed to start:

Caused by: java.lang.RuntimeException: Expecting to find exactly one HttpClient with osgi.jndi.service.name=claw/httpClientinstead, found 0

which is bizarre, because:

karaf@root()> service:list org.apache.http.client.HttpClient
[org.apache.http.client.HttpClient]
-----------------------------------
 osgi.jndi.service.name = claw/httpClient
 service.bundleid = 53
 service.id = 393
 service.scope = bundle
Provided by :
 islandora-http-client (53)

karaf@root()>

And why did it start up when I restarted it?

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

@birkland ++++

I will wait to hear from you. Thanks very much for all your work on this. (My only viable proffer of comfort is that a lot of Fedora repos are gong to have authN in place, so this should be useful to other people as well.)

@birkland
Copy link

birkland commented May 9, 2017

@ajs6f Also, good observation re: 0 HttpClient services. It seems impossible to tell blueprint to look for service while filtering by a property whose value is provided by configuration. So the end-around is to resolve the service manually. The downside is that the blueprint lifecycle won't delay activating the bundle if the service isn't resolved. So I think that's the issue here

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

@birkland Have you tried just using a direct <reference .../> element in the blueprint?

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

Here's a completely useless suggestion: https://issues.apache.org/jira/browse/ARIES-1665 says that we could switch the whole deal to CDI. :)

@birkland
Copy link

birkland commented May 9, 2017

@ajs6f Yes, the problem is that you can't do <reference filter="(osgi.jndi.service.name=${user.supplied.property}"); the property is not interpolated. If CDI-upon-OSGi makes things more straightforward or flexible, I'm all for it. I've been somewhat disappointed with blueprint the spec, and the Aires implementation/interpretation of it.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

Ah, I see. That's some stinky cheese, indeed. Actually, CDI isn't going to help as much with that particular problem, because I think you are actually using a difficult pattern by allowing the filter expression to vary at runtime. (Although I totally get why you are doing it.)

In CDI-OSGi, that might look like discarding all the XML and a more typesafe kind of injection:

public class HttpRegistry implements Registry {
    ....
    @Inject @CustomRepoClient @OSGiService Instance<HttpClient> clients;
    HttpClient client;
    ....
    {
        if (clients.isAmbiguous()) throw SomeException("Multiple custom clients");
        client = !clients.isUnsatisfied() ? clients.get() : buildDefaultClient();
    }

with custom client annotation like:

@Qualifier @Retention(RUNTIME) @Target({TYPE})
public @interface CustomRepoClient {}

to be used in bundles that supply a custom client. CDI-OSGi takes care of a lossless CDI qualifiers <-> OSGi filter expressions translation.

There are certainly other ways to do it, too. There are events to which to listen when services get registered and so forth, so that if a custom client shows up after the registry starts, the registry can start using it.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 9, 2017

@birkland There's also the CDI @Alternative machinery, especially the @Priority facility. That's some JBoss documention, but they're describing stuff right out of the spec-- it's just a nice clear explanation.

@birkland
Copy link

@ajs6f Well, I spent the afternoon OSGi fighting, and am still doing so.

I haven't had luck with using service priorities with blueprint in the past. Are service priorities in CDI-OSGI any better? Here's the issue:

What needs to happen is that if a bundle comes along that publishes an HttpClient service at a higher priority than the one that exists, then the new higher-priority HttpClient should be injected into all service references. Aby bundles that are already running are re-loaded and dependencies re-injected (now with the higher-priority service), much like what happens with a config change.

What actually happens is that blueprint makes a choice between all services available at the time once, injects once, and moves on. If a higher-ranked service pops into existence later on, it's ignored. So if CLAW is providing a third-party HttpClient, it would need to assure that the bundle is loaded and started before everything else. I worry that this is a burdensome thing to ask

As a result of all this, I think our hand is forced to service listeners (or equivalent). Will try and see what happens...

@birkland
Copy link

Well, re-injected is a little strong, I forgot that blueprint uses proxies underneath. In any case, here's the specific issue we're up against:

https://issues.apache.org/jira/browse/ARIES-1314

@ajs6f
Copy link
Contributor Author

ajs6f commented May 10, 2017

I completely agree about the "burden" of bundle ordering. In the OSGi field, demanding a particular load order for bundles is six kinds of wrong.

I do not know whether a given CDI-OSGi implementation (there are several) suffers from the same problem-- I will find out.

In the meantime, I think you are right that we need to be dropping back to programmatic work, directly with the service registry. And frankly, that's not too awful, in this case. We have only one service at stake and one client, right? (Or is there more than one client?)

Here's a possible design. Select a constant service location in JNDI. Then look for a service under that name. If there is more than one, an error. If there is one, use it. If there is not one, use a default client (not a service, just internal to the bundle that needs to do the work, the registry bundle, right?). Attach a service listener to the registry and if a service with that name shows up, use it. If more than one service with that name ever exists at a time, an error. So the semantics for that service change from "the client to use" to "the custom client to use, if there is one."

Does that sound plausible? I could probably get that into a PR fairly quickly. (He wrote, while packing up his office and becoming increasingly desperate about the potential to fit all of the plants into the back of the Prius in one load.)

@birkland
Copy link

@ajs6f Yes, that sounds totally plausible. I think that would need to happen within a proxy, which is the approach I've started exploring this morning

Right now, there's an org.fcrepo.apix.registry.HttpClientFetcher bean that provides a getClient() method which returns a org.apache.http.impl.client.CloseableHttpClient. Parts within API-X that need this client have the resulting HttpClient injected. In blueprint, this httpclient is available as a bean for injection via a factory pattern:

<reference id="httpClientFetcher" ext:proxy-method="classes"
interface="org.fcrepo.apix.registry.HttpClientFetcher" />

  <bean id="httpClient" factory-ref="httpClientFetcher"
factory-method="getClient" />

So I think the least invasive way do to this listening is within a proxy. That is to say, HttpClient produced by getClient() is a proxy that invokes the methods of some underlying delegate HttpClient. When a new HttpClient service is discovered, we'd need to update the underlying delegate.

I've just started to implement this in my local branch now. I already set up the ITs to use a Fedora protected by basic auth, and created a service in fcrepo-api-x-test which exports an HttpClient. The Karaf ITs load this bundle, and are currently displaying the "0 instances" problem you observed.

@birkland
Copy link

@ajs6f OK, I think the OSGi bits are working. I'm now diagnosing the inexplicable 400 requests from Fedora I now get when authenticating in the ITs.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 10, 2017

@birkland ++ are you using the CLAW vagrant? Or just a stock Karaf with a Fedora on the side?

@birkland
Copy link

@ajs6f I'm running all in the API-X ITs. A fedora instance protected by basic auth launched via the cargo maven plugin, an API-X instance in Karaf launched by pax exam, and an httpclient provided by a test bundle.

I wasn't creating the test HttpClient right. Now it looks like things are starting to work.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 10, 2017

Awesome! Let me know when you want me to fire it against claw-vagrant again.

@birkland
Copy link

OK, will do. Need to pick up the kids, but will hopefully have all ITs passing later tonight or tomorrow morning.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 10, 2017

No hurry-- I'm doing the same (family time) and trying to get through the last few tasks I have at UVa.

@birkland
Copy link

@ajs6f I just updated the PR, all ITs except for the service indexing IT are passing. The service indexing IT is camel-based, and uses the fcrepo-camel component to interact with Fedora. I'm looking onto that next, but @Ignored that IT for now so you can build the PR and try it out. In theory, it should work now!

It uses your suggestion to simply use OSGi service priority ranking, rather than looking for a service name in config. In particular:

  • If no HttpClient services are in the OSGi service registry, it uses a default HttpClient. New configuration properties were added so that this default client can use basic auth, if desired.
  • If one or more HttpClient services are in the OSGi service registry, then it uses the highest ranked one (according to standard OSGi service ranking).

The underlying implementation listens for OSGi binding events. So for the CLAW case, it should immediately start using the provided HttpClient whenever it becomes available to the service registry. Since the internal default HttpClient is not published to the OSGi service registry at all, the CLAW client will automatically win by virtue of being the only HttpClient service - so there's no need for you to specifically assign it any priority/ranking value.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 11, 2017

I will test this out today in a cage match against claw-vagrant. Thanks very much for your continued work on it!

@ajs6f
Copy link
Contributor Author

ajs6f commented May 11, 2017

https://www.youtube.com/watch?v=QuoKNZjr8_U

@ajs6f
Copy link
Contributor Author

ajs6f commented May 11, 2017

It worked! @dannylamb and I are arranging the CLAW PRs, but you will have to do a release of API-X with your PR merged to let us pick up the changes...

@birkland
Copy link

Yay! Excellent!. I think we can merge once the Camel-based ITs are resolved, and I add some text to the markdown documenting all of this. There shouldn't be a problem cutting a release quickly thereafter, I'll mention that prospect on today's API-X call.

@ajs6f
Copy link
Contributor Author

ajs6f commented May 17, 2017

I believe that this can now be safely merged to support islandora-deprecated/claw_vagrant#40.

@dannylamb
Copy link
Contributor

@ajs6f @birkland Circling back around to the one now that Islandoracon is over. I'll test both this and islandora-deprecated/claw_vagrant#40, and will let y'all know how it goes.

@dannylamb
Copy link
Contributor

Tested alongside islandora-deprecated/claw_vagrant#40

:shipit:

@dannylamb dannylamb merged commit 2c35c55 into Islandora:master May 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants