Browse Source

Merge "Added Glance Service"

tags/0.0.1
Jenkins 2 years ago
parent
commit
88696bb420
6 changed files with 510 additions and 3 deletions
  1. 11
    3
      configure-devstack.js
  2. 109
    0
      src/glance.js
  3. 82
    0
      test/functional/glanceTest.js
  4. 190
    0
      test/unit/glanceTest.js
  5. 114
    0
      test/unit/helpers/data/glance.js
  6. 4
    0
      vagrant.sh

+ 11
- 3
configure-devstack.js View File

@@ -5,9 +5,17 @@ import path from 'path';
5 5
 
6 6
 function getDevstackConfig() {
7 7
   const karmaConfig = karma.parseConfig(path.resolve('./karma.conf.js'));
8
-  return "[[post-config|$KEYSTONE_CONF]]\n" +
9
-    "[cors]\n" +
10
-    "allowed_origin=http://localhost:" + karmaConfig.port + "\n";
8
+
9
+  return getCorsConfig('$KEYSTONE_CONF', karmaConfig) +
10
+    getCorsConfig('$GLANCE_API_CONF', karmaConfig);
11
+
12
+}
13
+
14
+function getCorsConfig(service, karmaConfig) {
15
+  return `[[post-config|${service}]]
16
+[cors]
17
+allowed_origin=http://localhost:${karmaConfig.port}
18
+`;
11 19
 }
12 20
 
13 21
 fs.appendFile(process.env.BASE + '/new/devstack/local.conf', getDevstackConfig(), (err) => {

+ 109
- 0
src/glance.js View File

@@ -0,0 +1,109 @@
1
+/*
2
+ * Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
+ * use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
13
+ * the License for the specific language governing permissions and limitations
14
+ * under the License.
15
+ */
16
+import Http from './util/http';
17
+
18
+/**
19
+ * A list of all supported versions. Please keep this array sorted by most recent.
20
+ *
21
+ * @type {Array}
22
+ * @ignore
23
+ */
24
+const supportedGlanceVersions = [
25
+  'v2.3'
26
+];
27
+
28
+export default class Glance {
29
+
30
+  /**
31
+   * This class provides direct, idempotent, low-level access to the Glance API of a specific
32
+   * cloud. The constructor requires that you provide a specific glance interface endpoint
33
+   * descriptor, as received from keystone's catalog list.
34
+   *
35
+   * @example
36
+   * {
37
+   *   region_id: "RegionOne",
38
+   *   url: "http://127.0.0.1:9292",
39
+   *   region: "RegionOne",
40
+   *   interface: "admin",
41
+   *   id: "0b8b5f0f14904136ab5a4f83f27ec49a"
42
+   * }
43
+   * @param {{}} endpointConfig The configuration element for a specific glance endpoint.
44
+   */
45
+  constructor (endpointConfig) {
46
+    // Sanity checks.
47
+    if (!endpointConfig || !endpointConfig.url) {
48
+      throw new Error('An endpoint configuration is required.');
49
+    }
50
+    // Clone the config, so that this instance is immutable
51
+    // at runtime (no modifying the config after the fact).
52
+    this._config = Object.assign({}, endpointConfig);
53
+    this.http = new Http();
54
+  }
55
+
56
+  /**
57
+   * Retrieve all the API versions available.
58
+   *
59
+   * @returns {Promise.<T>} A promise that will resolve with the list of API versions.
60
+   */
61
+  versions () {
62
+    return this.http
63
+      .httpGet(this._config.url)
64
+      .then((response) => response.json())
65
+      .then((body) => body.versions);
66
+  }
67
+
68
+  /**
69
+   * Retrieve the API version declaration that is currently in use by this glance API.
70
+   *
71
+   * @returns {Promise.<T>} A promise that will resolve with the specific API version.
72
+   */
73
+  version () {
74
+    return this
75
+      .versions()
76
+      .then((versions) => {
77
+        const version = versions.find((element) => {
78
+          return supportedGlanceVersions.indexOf(element.id) > -1;
79
+        });
80
+        if (version) {
81
+          return version;
82
+        }
83
+        throw new Error("No supported Glance API version available.");
84
+      });
85
+  }
86
+
87
+  /**
88
+   * Return the root API endpoint for the current supported glance version.
89
+   *
90
+   * @returns {Promise.<T>|*} A promise which will resolve with the endpoint URL string.
91
+   */
92
+  serviceEndpoint () {
93
+    if (!this._endpointPromise) {
94
+      this._endpointPromise = this.version()
95
+        .then((version) => {
96
+          if (version.links) {
97
+            for (let i = 0; i < version.links.length; i++) {
98
+              let link = version.links[i];
99
+              if (link.rel === 'self' && link.href) {
100
+                return link.href;
101
+              }
102
+            }
103
+          }
104
+          throw new Error("No service endpoint discovered.");
105
+        });
106
+    }
107
+    return this._endpointPromise;
108
+  }
109
+}

+ 82
- 0
test/functional/glanceTest.js View File

@@ -0,0 +1,82 @@
1
+/*
2
+ * Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
+ * use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
13
+ * the License for the specific language governing permissions and limitations
14
+ * under the License.
15
+ */
16
+
17
+import config from "./helpers/cloudsConfig";
18
+import Version from '../../src/util/version';
19
+import Glance from "../../src/glance";
20
+import Keystone from "../../src/keystone";
21
+import log from 'loglevel';
22
+
23
+log.setLevel("DEBUG");
24
+
25
+describe("Glance", () => {
26
+  // Create a keystone instance and extract the glance API endpoint.
27
+  let devstackConfig = config.clouds.devstack;
28
+  let keystone = new Keystone(devstackConfig);
29
+  let tokenPromise = keystone.tokenIssue();
30
+
31
+  let configPromise = tokenPromise
32
+    .then((token) => keystone.catalogList(token))
33
+    .then((catalog) => catalog.find((entry) => entry.name === 'glance'))
34
+    .then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public'));
35
+
36
+  describe("versions()", () => {
37
+    it("should return a list of all versions available on this clouds' glance", (done) => {
38
+      configPromise
39
+        .then((config) => new Glance(config))
40
+        .then((glance) => glance.versions())
41
+        .then((versions) => {
42
+          // Quick sanity check.
43
+          expect(versions.length > 0).toBeTruthy();
44
+          done();
45
+        })
46
+        .catch((error) => done.fail(error));
47
+    });
48
+  });
49
+
50
+  describe("version()", () => {
51
+
52
+    const supportedApiVersions = [
53
+      new Version('image 2.3')
54
+    ];
55
+
56
+    /**
57
+     * This test acts as a canary, to inform the SDK developers that the Glance API
58
+     * has changed in a significant way.
59
+     */
60
+    it("should return a supported version.", (done) => {
61
+      configPromise
62
+        .then((config) => new Glance(config))
63
+        .then((glance) => glance.version())
64
+        .then((version) => {
65
+
66
+          // Quick sanity check.
67
+          const apiVersion = new Version('image', version.id);
68
+
69
+          for (let i = 0; i < supportedApiVersions.length; i++) {
70
+            let supportedVersion = supportedApiVersions[i];
71
+            if (apiVersion.equals(supportedVersion)) {
72
+              done();
73
+              return;
74
+            }
75
+          }
76
+          fail("Current devstack glance version is not supported.");
77
+          done();
78
+        })
79
+        .catch((error) => done.fail(error));
80
+    });
81
+  });
82
+});

+ 190
- 0
test/unit/glanceTest.js View File

@@ -0,0 +1,190 @@
1
+/*
2
+ * Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
+ * use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
13
+ * the License for the specific language governing permissions and limitations
14
+ * under the License.
15
+ */
16
+
17
+import Glance from '../../src/glance.js';
18
+import * as mockData from './helpers/data/glance';
19
+import fetchMock from 'fetch-mock';
20
+
21
+describe('Glance', () => {
22
+
23
+  afterEach(fetchMock.restore);
24
+
25
+  it('should export a class', () => {
26
+    const glance = new Glance(mockData.config);
27
+    expect(glance).toBeDefined();
28
+  });
29
+
30
+  it('should throw an error for an empty config', () => {
31
+    try {
32
+      const glance = new Glance();
33
+      glance.versions();
34
+    } catch (e) {
35
+      expect(e.message).toEqual('An endpoint configuration is required.');
36
+    }
37
+  });
38
+
39
+  describe("versions()", () => {
40
+    it("Should return a list of all versions available on this clouds' glance", (done) => {
41
+      const glance = new Glance(mockData.config);
42
+
43
+      fetchMock.mock(mockData.root());
44
+
45
+      glance.versions()
46
+        .then((versions) => {
47
+          // Quick sanity check.
48
+          expect(versions.length).toBe(6);
49
+          done();
50
+        })
51
+        .catch((error) => done.fail(error));
52
+    });
53
+
54
+    it("Should NOT cache its results", (done) => {
55
+      const glance = new Glance(mockData.config);
56
+      const mockOptions = mockData.root();
57
+
58
+      fetchMock.mock(mockOptions);
59
+
60
+      glance.versions()
61
+        .then(() => {
62
+          // Validate that the mock has only been invoked once
63
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(1);
64
+          return glance.versions();
65
+        })
66
+        .then(() => {
67
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(2);
68
+          done();
69
+        })
70
+        .catch((error) => done.fail(error));
71
+    });
72
+  });
73
+
74
+  describe("version()", () => {
75
+
76
+    it("Should return a supported version of the glance API.", (done) => {
77
+      const glance = new Glance(mockData.config);
78
+
79
+      fetchMock.mock(mockData.root());
80
+
81
+      glance.version()
82
+        .then((version) => {
83
+          expect(version.id).toEqual('v2.3');
84
+          done();
85
+        })
86
+        .catch((error) => done.fail(error));
87
+    });
88
+
89
+    it("Should throw an exception if no supported version is found.", (done) => {
90
+      const glance = new Glance(mockData.config);
91
+
92
+      // Build an invalid mock object.
93
+      const mockOptions = mockData.root();
94
+      mockOptions.response.versions.shift();
95
+
96
+      fetchMock.mock(mockOptions);
97
+
98
+      glance.version()
99
+        .then((response) => done.fail(response))
100
+        .catch((error) => {
101
+          expect(error).not.toBeNull();
102
+          done();
103
+        });
104
+    });
105
+
106
+    it("Should NOT cache its results", (done) => {
107
+      const glance = new Glance(mockData.config);
108
+      const mockOptions = mockData.root();
109
+      fetchMock.mock(mockOptions);
110
+
111
+      glance.version()
112
+        .then(() => {
113
+          // Validate that the mock has only been invoked once
114
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(1);
115
+          return glance.version();
116
+        })
117
+        .then(() => {
118
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(2);
119
+          done();
120
+        })
121
+        .catch((error) => done.fail(error));
122
+    });
123
+  });
124
+
125
+  describe("serviceEndpoint()", () => {
126
+
127
+    it("Should return a valid endpoint to the glance API.", (done) => {
128
+      const glance = new Glance(mockData.config);
129
+
130
+      fetchMock.mock(mockData.root());
131
+
132
+      glance.serviceEndpoint()
133
+        .then((endpoint) => {
134
+          expect(endpoint).toEqual('http://192.168.99.99:9292/v2/');
135
+          done();
136
+        })
137
+        .catch((error) => done.fail(error));
138
+    });
139
+
140
+    it("Should throw an exception if no endpoint is provided.", (done) => {
141
+      const glance = new Glance(mockData.config);
142
+
143
+      // Build an exception payload.
144
+      const mockOptions = mockData.root();
145
+      mockOptions.response.versions[0].links = [];
146
+      fetchMock.mock(mockOptions);
147
+
148
+      glance.serviceEndpoint()
149
+        .then((response) => done.fail(response))
150
+        .catch((error) => {
151
+          expect(error).not.toBeNull();
152
+          done();
153
+        });
154
+    });
155
+
156
+    it("Should throw an exception if no links array exists.", (done) => {
157
+      const glance = new Glance(mockData.config);
158
+
159
+      // Build an exception payload.
160
+      const mockOptions = mockData.root();
161
+      delete mockOptions.response.versions[0].links;
162
+      fetchMock.mock(mockOptions);
163
+
164
+      glance.serviceEndpoint()
165
+        .then((response) => done.fail(response))
166
+        .catch((error) => {
167
+          expect(error).not.toBeNull();
168
+          done();
169
+        });
170
+    });
171
+
172
+    it("Should cache its results", (done) => {
173
+      const glance = new Glance(mockData.config);
174
+      const mockOptions = mockData.root();
175
+      fetchMock.mock(mockOptions);
176
+
177
+      glance.serviceEndpoint()
178
+        .then(() => {
179
+          // Validate that the mock has only been invoked once
180
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(1);
181
+          return glance.serviceEndpoint();
182
+        })
183
+        .then(() => {
184
+          expect(fetchMock.calls(mockOptions.name).length).toEqual(1);
185
+          done();
186
+        })
187
+        .catch((error) => done.fail(error));
188
+    });
189
+  });
190
+});

+ 114
- 0
test/unit/helpers/data/glance.js View File

@@ -0,0 +1,114 @@
1
+/*
2
+ * Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
+ * use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
13
+ * the License for the specific language governing permissions and limitations
14
+ * under the License.
15
+ */
16
+
17
+/**
18
+ * This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
19
+ * keystone. Most of these are functions, as FetchMock does not perform a safe clone of the
20
+ * instances, and may accidentally modify them at runtime.
21
+ */
22
+
23
+/**
24
+ * Mock cloud configuration that matches our test data below. This is not a full clouds.yaml
25
+ * format, rather just the subsection pointing to a particular cloud.
26
+ */
27
+const glanceConfig = {
28
+  region_id: "RegionOne",
29
+  url: "http://192.168.99.99:9292/",
30
+  region: "RegionOne",
31
+  interface: "public",
32
+  id: "0b8b5f0f14904136ab5a4f83f27ec49a"
33
+};
34
+
35
+/**
36
+ * Build a new FetchMock configuration for the root endpoint.
37
+ *
38
+ * @returns {{}} A full FetchMock configuration for Glance's Root Resource.
39
+ */
40
+function rootResponse () {
41
+  return {
42
+    method: 'GET',
43
+    matcher: 'http://192.168.99.99:9292/',
44
+    response: {
45
+      versions: [
46
+        {
47
+          status: "CURRENT",
48
+          id: "v2.3",
49
+          links: [
50
+            {
51
+              href: "http://192.168.99.99:9292/v2/",
52
+              rel: "self"
53
+            }
54
+          ]
55
+        },
56
+        {
57
+          status: "SUPPORTED",
58
+          id: "v2.2",
59
+          links: [
60
+            {
61
+              href: "http://192.168.99.99:9292/v2/",
62
+              rel: "self"
63
+            }
64
+          ]
65
+        },
66
+        {
67
+          status: "SUPPORTED",
68
+          id: "v2.1",
69
+          links: [
70
+            {
71
+              href: "http://192.168.99.99:9292/v2/",
72
+              rel: "self"
73
+            }
74
+          ]
75
+        },
76
+        {
77
+          status: "SUPPORTED",
78
+          id: "v2.0",
79
+          links: [
80
+            {
81
+              href: "http://192.168.99.99:9292/v2/",
82
+              rel: "self"
83
+            }
84
+          ]
85
+        },
86
+        {
87
+          status: "SUPPORTED",
88
+          id: "v1.1",
89
+          links: [
90
+            {
91
+              href: "http://192.168.99.99:9292/v1/",
92
+              rel: "self"
93
+            }
94
+          ]
95
+        },
96
+        {
97
+          status: "SUPPORTED",
98
+          id: "v1.0",
99
+          links: [
100
+            {
101
+              href: "http://192.168.99.99:9292/v1/",
102
+              rel: "self"
103
+            }
104
+          ]
105
+        }
106
+      ]
107
+    }
108
+  };
109
+}
110
+
111
+export {
112
+  glanceConfig as config,
113
+  rootResponse as root
114
+};

+ 4
- 0
vagrant.sh View File

@@ -46,6 +46,10 @@ RECLONE=True
46 46
 [[post-config|\$KEYSTONE_CONF]]
47 47
 [cors]
48 48
 allowed_origin=http://localhost:9876
49
+
50
+[[post-config|\$GLANCE_API_CONF]]
51
+[cors]
52
+allowed_origin=http://localhost:9876
49 53
 EOL
50 54
 
51 55
 # Start devstack.

Loading…
Cancel
Save