Browse Source

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
Michael Krotscheck 2 years ago
parent
commit
abfe901b5e
No account linked to committer's email address

+ 2
- 1
package.json View File

@@ -31,7 +31,8 @@
31 31
   "dependencies": {
32 32
     "babel-runtime": "^6.11.6",
33 33
     "isomorphic-fetch": "^2.2.1",
34
-    "loglevel": "^1.4.1"
34
+    "loglevel": "^1.4.1",
35
+    "url-parse": "^1.1.3"
35 36
   },
36 37
   "devDependencies": {
37 38
     "babel-cli": "^6.10.1",

+ 21
- 4
src/util/abstract_service.js View File

@@ -15,6 +15,7 @@
15 15
  */
16 16
 
17 17
 import Http from './http';
18
+import URL from 'url-parse';
18 19
 
19 20
 export default class AbstractService {
20 21
 
@@ -57,10 +58,26 @@ export default class AbstractService {
57 58
    * @returns {Promise.<T>} A promise that will resolve with the list of API versions.
58 59
    */
59 60
   versions () {
60
-    return this.http
61
-      .httpGet(this._endpointUrl)
62
-      .then((response) => response.json())
63
-      .then((body) => body.versions);
61
+    return new Promise((resolve, reject) => {
62
+      let promise = this.http
63
+        .httpGet(this._endpointUrl)
64
+        .catch((response) => {
65
+          if (response.status === 401) {
66
+            let rootUrl = new URL(this._endpointUrl);
67
+            rootUrl.set('pathname', '/');
68
+            rootUrl.set('query', '');
69
+            rootUrl.set('hash', '');
70
+
71
+            return this.http.httpGet(rootUrl.href);
72
+          } else {
73
+            return reject(response);
74
+          }
75
+        });
76
+
77
+      promise
78
+        .then((response) => response.json())
79
+        .then((body) => resolve(body.versions));
80
+    });
64 81
   }
65 82
 
66 83
   /**

+ 21
- 2
test/unit/helpers/data/versions.js View File

@@ -23,6 +23,7 @@
23 23
  * URLs to match the test data below.
24 24
  */
25 25
 const rootUrl = "http://example.com/";
26
+const subUrl = `${rootUrl}/v1`;
26 27
 
27 28
 /**
28 29
  * A mock list of supported versions for the below requests.
@@ -36,7 +37,7 @@ const versions = [
36 37
 /**
37 38
  * Build a new FetchMock configuration for the versions (root) endpoint.
38 39
  *
39
- * @returns {{}} A full FetchMock configuration for Glance's Root Resource.
40
+ * @returns {{}} A full FetchMock configuration for a versions resource.
40 41
  */
41 42
 function rootResponse() {
42 43
   return {
@@ -109,8 +110,26 @@ function rootResponse() {
109 110
   };
110 111
 }
111 112
 
113
+/**
114
+ * FetchMock configuration for a 401 response against the versioned API url.
115
+ *
116
+ * @param {int} httpStatus The HTTP status for the response.
117
+ * @returns {{}} A full FetchMock configuration a failing request..
118
+ */
119
+function subResponse(httpStatus = 401) {
120
+  return {
121
+    method: 'GET',
122
+    matcher: subUrl,
123
+    response: {
124
+      status: httpStatus
125
+    }
126
+  };
127
+}
128
+
112 129
 export {
113 130
   rootUrl,
131
+  subUrl,
114 132
   versions,
115
-  rootResponse
133
+  rootResponse,
134
+  subResponse
116 135
 };

+ 30
- 0
test/unit/util/abstract_serviceTest.js View File

@@ -73,6 +73,36 @@ describe('AbstractService', () => {
73 73
         .catch((error) => done.fail(error));
74 74
     });
75 75
 
76
+    // This test catches the case when the service catalog already points
77
+    // at an API version.
78
+    it("Should retry at the root URL if a 401 is encountered", (done) => {
79
+      const service = new AbstractService(mockData.subUrl, mockData.versions);
80
+
81
+      fetchMock.mock(mockData.subResponse());
82
+      fetchMock.mock(mockData.rootResponse());
83
+
84
+      service.versions()
85
+        .then((versions) => {
86
+          // Quick sanity check.
87
+          expect(versions.length).toBe(6);
88
+          done();
89
+        })
90
+        .catch((error) => done.fail(error));
91
+    });
92
+
93
+    it("Should not retry at the root URL if a different status is encountered", (done) => {
94
+      const service = new AbstractService(mockData.subUrl, mockData.versions);
95
+
96
+      fetchMock.mock(mockData.subResponse(500));
97
+
98
+      service.versions()
99
+        .then((response) => done.fail(response))
100
+        .catch((error) => {
101
+          expect(error).not.toBeNull();
102
+          done();
103
+        });
104
+    });
105
+
76 106
     it("Should NOT cache its results", (done) => {
77 107
       const service = new AbstractService(mockData.rootUrl, mockData.versions);
78 108
       const mockOptions = mockData.rootResponse();

Loading…
Cancel
Save