AbstractService will try to detect the versions resource in more places.

We have no guarantees that the keystone service catalog will have the
root resource of any given service registered. As most versioned API
endpoints require tokens, we can reasonably assume that a 401 will
be encountered. This patch adds an extra check against the response
from the provided URL, and should a 401 be encountered, attempts
to resolve the versions from the root resource of the provided URL.

Change-Id: I655409f0eb9bfbd3489827db46faef026ede82f9
This commit is contained in:
Michael Krotscheck 2016-09-13 21:55:26 -07:00
parent b60cb86755
commit abfe901b5e
No known key found for this signature in database
GPG Key ID: 20E618D878DE38AB
4 changed files with 74 additions and 7 deletions

View File

@ -31,7 +31,8 @@
"dependencies": {
"babel-runtime": "^6.11.6",
"isomorphic-fetch": "^2.2.1",
"loglevel": "^1.4.1"
"loglevel": "^1.4.1",
"url-parse": "^1.1.3"
},
"devDependencies": {
"babel-cli": "^6.10.1",

View File

@ -15,6 +15,7 @@
*/
import Http from './http';
import URL from 'url-parse';
export default class AbstractService {
@ -57,10 +58,26 @@ export default class AbstractService {
* @returns {Promise.<T>} A promise that will resolve with the list of API versions.
*/
versions () {
return this.http
.httpGet(this._endpointUrl)
.then((response) => response.json())
.then((body) => body.versions);
return new Promise((resolve, reject) => {
let promise = this.http
.httpGet(this._endpointUrl)
.catch((response) => {
if (response.status === 401) {
let rootUrl = new URL(this._endpointUrl);
rootUrl.set('pathname', '/');
rootUrl.set('query', '');
rootUrl.set('hash', '');
return this.http.httpGet(rootUrl.href);
} else {
return reject(response);
}
});
promise
.then((response) => response.json())
.then((body) => resolve(body.versions));
});
}
/**

View File

@ -23,6 +23,7 @@
* URLs to match the test data below.
*/
const rootUrl = "http://example.com/";
const subUrl = `${rootUrl}/v1`;
/**
* A mock list of supported versions for the below requests.
@ -36,7 +37,7 @@ const versions = [
/**
* Build a new FetchMock configuration for the versions (root) endpoint.
*
* @returns {{}} A full FetchMock configuration for Glance's Root Resource.
* @returns {{}} A full FetchMock configuration for a versions resource.
*/
function rootResponse() {
return {
@ -109,8 +110,26 @@ function rootResponse() {
};
}
/**
* FetchMock configuration for a 401 response against the versioned API url.
*
* @param {int} httpStatus The HTTP status for the response.
* @returns {{}} A full FetchMock configuration a failing request..
*/
function subResponse(httpStatus = 401) {
return {
method: 'GET',
matcher: subUrl,
response: {
status: httpStatus
}
};
}
export {
rootUrl,
subUrl,
versions,
rootResponse
rootResponse,
subResponse
};

View File

@ -73,6 +73,36 @@ describe('AbstractService', () => {
.catch((error) => done.fail(error));
});
// This test catches the case when the service catalog already points
// at an API version.
it("Should retry at the root URL if a 401 is encountered", (done) => {
const service = new AbstractService(mockData.subUrl, mockData.versions);
fetchMock.mock(mockData.subResponse());
fetchMock.mock(mockData.rootResponse());
service.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(6);
done();
})
.catch((error) => done.fail(error));
});
it("Should not retry at the root URL if a different status is encountered", (done) => {
const service = new AbstractService(mockData.subUrl, mockData.versions);
fetchMock.mock(mockData.subResponse(500));
service.versions()
.then((response) => done.fail(response))
.catch((error) => {
expect(error).not.toBeNull();
done();
});
});
it("Should NOT cache its results", (done) => {
const service = new AbstractService(mockData.rootUrl, mockData.versions);
const mockOptions = mockData.rootResponse();