Browse Source

Merge "Created Nova service with flavorList method."

Jenkins 2 years ago
parent
commit
e8d6bcdda6
6 changed files with 414 additions and 1 deletions
  1. 2
    1
      configure-devstack.js
  2. 72
    0
      src/nova.js
  3. 74
    0
      test/functional/novaTest.js
  4. 187
    0
      test/unit/helpers/data/nova.js
  5. 75
    0
      test/unit/novaTest.js
  6. 4
    0
      vagrant.sh

+ 2
- 1
configure-devstack.js View File

@@ -8,7 +8,8 @@ function getDevstackConfig() {
8 8
 
9 9
   return getCorsConfig('$KEYSTONE_CONF', karmaConfig) +
10 10
     getCorsConfig('$GLANCE_API_CONF', karmaConfig) +
11
-    getCorsConfig('$NEUTRON_CONF', karmaConfig);
11
+    getCorsConfig('$NEUTRON_CONF', karmaConfig) +
12
+    getCorsConfig('$NOVA_CONF', karmaConfig);
12 13
 
13 14
 }
14 15
 

+ 72
- 0
src/nova.js View File

@@ -0,0 +1,72 @@
1
+/*
2
+ * Copyright (c) 2016 Michael Krotscheck.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy 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,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import AbstractService from "./util/abstractService";
18
+
19
+/**
20
+ * A list of all supported versions. Please keep this array sorted by most recent.
21
+ *
22
+ * @type {Array}
23
+ * @ignore
24
+ */
25
+const supportedNovaVersions = [
26
+  'v2.1'
27
+];
28
+
29
+export default class Nova extends AbstractService {
30
+
31
+  /**
32
+   * This class provides direct, idempotent, low-level access to the Nova API of a specific
33
+   * cloud. The constructor requires that you provide a specific nova interface endpoint
34
+   * descriptor, as received from keystone's catalog list.
35
+   *
36
+   * @example
37
+   * {
38
+   *   region_id: "RegionOne",
39
+   *   url: "http://127.0.0.1:8774/",
40
+   *   region: "RegionOne",
41
+   *   interface: "admin",
42
+   *   id: "0b8b5f0f14904136ab5a4f83f27ec49a"
43
+   * }
44
+   * @param {{}} endpointConfig The configuration element for a specific nova endpoint.
45
+   */
46
+  constructor(endpointConfig) {
47
+    // Sanity checks.
48
+    if (!endpointConfig || !endpointConfig.url) {
49
+      throw new Error('An endpoint configuration is required.');
50
+    }
51
+    // Clone the config, so that this instance is immutable
52
+    // at runtime (no modifying the config after the fact).
53
+    endpointConfig = Object.assign({}, endpointConfig);
54
+
55
+    super(endpointConfig.url, supportedNovaVersions);
56
+    this._config = endpointConfig;
57
+  }
58
+
59
+  /**
60
+   * List the flavors available on nova.
61
+   *
62
+   * @param {String} token An authorization token, or a promise which will resolve into one.
63
+   * @returns {Promise.<T>} A promise which will resolve with the list of flavors.
64
+   */
65
+  flavorList(token = null) {
66
+    return this
67
+      ._requestComponents(token)
68
+      .then(([url, headers]) => this.http.httpRequest('GET', `${url}flavors`, headers))
69
+      .then((response) => response.json())
70
+      .then((body) => body.flavors);
71
+  }
72
+}

+ 74
- 0
test/functional/novaTest.js View File

@@ -0,0 +1,74 @@
1
+/*
2
+ * Copyright (c) 2016 Michael Krotscheck.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy 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,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import config from "./helpers/cloudsConfig";
18
+import Version from '../../src/util/version';
19
+import Nova from "../../src/nova";
20
+import Keystone from "../../src/keystone";
21
+import log from 'loglevel';
22
+
23
+log.setLevel("DEBUG");
24
+
25
+describe("Nova", () => {
26
+  // Create a keystone instance and extract the nova 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 === 'nova'))
34
+    .then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public'));
35
+
36
+  describe("version()", () => {
37
+    const supportedApiVersions = [
38
+      new Version('2.1')
39
+    ];
40
+
41
+    /**
42
+     * This test acts as a canary, to inform the SDK developers that the Nova API
43
+     * has changed in a significant way.
44
+     */
45
+    it("should return a supported version.", (done) => {
46
+      configPromise
47
+        .then((config) => new Nova(config))
48
+        .then((nova) => nova.version())
49
+        .then((apiVersion) => {
50
+          let found = supportedApiVersions.find((item) => item.equals(apiVersion));
51
+          expect(found).not.toBeFalsy();
52
+          done();
53
+        })
54
+        .catch((error) => done.fail(error));
55
+    });
56
+  });
57
+
58
+  describe("flavorList()", () => {
59
+
60
+    /**
61
+     * Assert that we can get a list of flavors.
62
+     */
63
+    it("should return a list of flavors.", (done) => {
64
+      configPromise
65
+        .then((config) => new Nova(config))
66
+        .then((nova) => nova.flavorList(tokenPromise))
67
+        .then((flavors) => {
68
+          expect(flavors.length > 0).toBeTruthy();
69
+          done();
70
+        })
71
+        .catch((error) => done.fail(error));
72
+    });
73
+  });
74
+});

+ 187
- 0
test/unit/helpers/data/nova.js View File

@@ -0,0 +1,187 @@
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
+ * nova. 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
+ * A catalog entry that matches what we expect from the Keystone Catalog for nova compute.
25
+ */
26
+const novaConfig = {
27
+  region_id: "RegionOne",
28
+  url: "http://192.168.99.99:8774/v2.1",
29
+  region: "RegionOne",
30
+  interface: "public",
31
+  id: "be681632633d4a62a781148c2fedd6aa"
32
+};
33
+
34
+/**
35
+ * Build a new FetchMock configuration for the root endpoint.
36
+ *
37
+ * @returns {{}} A full FetchMock configuration for Nova's Root Resource.
38
+ */
39
+function rootResponse() {
40
+  return {
41
+    method: 'GET',
42
+    matcher: 'http://192.168.99.99:8774/',
43
+    response: {
44
+      versions: [{
45
+        status: "CURRENT",
46
+        updated: "2013-07-23T11:33:21Z",
47
+        links: [{href: "http://192.168.99.99:8774/v2.1/", rel: "self"}],
48
+        min_version: "2.1",
49
+        version: "2.38",
50
+        id: "v2.1"
51
+      }, {
52
+        status: "SUPPORTED",
53
+        updated: "2011-01-21T11:33:21Z",
54
+        links: [{href: "http://192.168.99.99:8774/v2/", rel: "self"}],
55
+        min_version: "",
56
+        version: "",
57
+        id: "v2.0"
58
+      }]
59
+    }
60
+  };
61
+}
62
+
63
+/**
64
+ * Create a FAILING response to the version endpoint.
65
+ *
66
+ * @param {String} version The version ID.
67
+ * @return {{}} A FetchMock configuration for this request's response.
68
+ */
69
+function versionedRootResponse(version = 'v2.1') {
70
+  return {
71
+    method: 'GET',
72
+    matcher: `http://192.168.99.99:8774/${version}`,
73
+    response: {
74
+      status: 401
75
+    }
76
+  };
77
+}
78
+
79
+/**
80
+ * Simulate an imageList response.
81
+ *
82
+ * @param {String} token An auth token.
83
+ * @return {{}} A FetchMock configuration for this request's response.
84
+ */
85
+function flavorList(token) {
86
+  return {
87
+    method: 'GET',
88
+    matcher: 'http://192.168.99.99:8774/v2.1/flavors',
89
+    headers: {
90
+      'X-Auth-Token': token
91
+    },
92
+    response: {
93
+      flavors: [{
94
+        id: "1",
95
+        links: [
96
+          {href: "http://192.168.99.99:8774/v2.1/flavors/1", rel: "self"},
97
+          {href: "http://192.168.99.99:8774/flavors/1", rel: "bookmark"}
98
+        ],
99
+        name: "m1.tiny"
100
+      }, {
101
+        id: "2",
102
+        links: [
103
+          {href: "http://192.168.99.99:8774/v2.1/flavors/2", rel: "self"},
104
+          {href: "http://192.168.99.99:8774/flavors/2", rel: "bookmark"}
105
+        ],
106
+        name: "m1.small"
107
+      }, {
108
+        id: "3",
109
+        links: [
110
+          {href: "http://192.168.99.99:8774/v2.1/flavors/3", rel: "self"},
111
+          {href: "http://192.168.99.99:8774/flavors/3", rel: "bookmark"}
112
+        ],
113
+        name: "m1.medium"
114
+      }, {
115
+        id: "4",
116
+        links: [
117
+          {href: "http://192.168.99.99:8774/v2.1/flavors/4", rel: "self"},
118
+          {href: "http://192.168.99.99:8774/flavors/4", rel: "bookmark"}
119
+        ],
120
+        name: "m1.large"
121
+      }, {
122
+        id: "42",
123
+        links: [
124
+          {href: "http://192.168.99.99:8774/v2.1/flavors/42", rel: "self"},
125
+          {href: "http://192.168.99.99:8774/flavors/42", rel: "bookmark"}
126
+        ],
127
+        name: "m1.nano"
128
+      }, {
129
+        id: "5",
130
+        links: [
131
+          {href: "http://192.168.99.99:8774/v2.1/flavors/5", rel: "self"},
132
+          {href: "http://192.168.99.99:8774/flavors/5", rel: "bookmark"}
133
+        ],
134
+        name: "m1.xlarge"
135
+      }, {
136
+        id: "84",
137
+        links: [
138
+          {href: "http://192.168.99.99:8774/v2.1/flavors/84", rel: "self"},
139
+          {href: "http://192.168.99.99:8774/flavors/84", rel: "bookmark"}
140
+        ],
141
+        name: "m1.micro"
142
+      }, {
143
+        id: "c1",
144
+        links: [
145
+          {href: "http://192.168.99.99:8774/v2.1/flavors/c1", rel: "self"},
146
+          {href: "http://192.168.99.99:8774/flavors/c1", rel: "bookmark"}
147
+        ],
148
+        name: "cirros256"
149
+      }, {
150
+        id: "d1",
151
+        links: [
152
+          {href: "http://192.168.99.99:8774/v2.1/flavors/d1", rel: "self"},
153
+          {href: "http://192.168.99.99:8774/flavors/d1", rel: "bookmark"}
154
+        ],
155
+        name: "ds512M"
156
+      }, {
157
+        id: "d2",
158
+        links: [
159
+          {href: "http://192.168.99.99:8774/v2.1/flavors/d2", rel: "self"},
160
+          {href: "http://192.168.99.99:8774/flavors/d2", rel: "bookmark"}
161
+        ],
162
+        name: "ds1G"
163
+      }, {
164
+        id: "d3",
165
+        links: [
166
+          {href: "http://192.168.99.99:8774/v2.1/flavors/d3", rel: "self"},
167
+          {href: "http://192.168.99.99:8774/flavors/d3", rel: "bookmark"}
168
+        ],
169
+        name: "ds2G"
170
+      }, {
171
+        id: "d4",
172
+        links: [
173
+          {href: "http://192.168.99.99:8774/v2.1/flavors/d4", rel: "self"},
174
+          {href: "http://192.168.99.99:8774/flavors/d4", rel: "bookmark"}
175
+        ],
176
+        name: "ds4G"
177
+      }]
178
+    }
179
+  };
180
+}
181
+
182
+export {
183
+  novaConfig as config,
184
+  rootResponse as root,
185
+  versionedRootResponse as rootVersion,
186
+  flavorList
187
+};

+ 75
- 0
test/unit/novaTest.js View File

@@ -0,0 +1,75 @@
1
+/*
2
+ * Copyright (c) 2016 Michael Krotscheck.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy 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,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import Nova from "../../src/nova.js";
18
+import * as mockData from "./helpers/data/nova";
19
+import fetchMock from "fetch-mock";
20
+
21
+describe('Nova', () => {
22
+
23
+  afterEach(fetchMock.restore);
24
+
25
+  it('should export a class', () => {
26
+    const nova = new Nova(mockData.config);
27
+    expect(nova).toBeDefined();
28
+  });
29
+
30
+  it('should throw an error for an empty config', () => {
31
+    expect(() => new Nova(null)).toThrow();
32
+  });
33
+
34
+  describe("flavorList()", () => {
35
+    let nova = null;
36
+
37
+    beforeEach(() => {
38
+      fetchMock.mock(mockData.rootVersion());
39
+      fetchMock.mock(mockData.root());
40
+      nova = new Nova(mockData.config);
41
+    });
42
+
43
+    it("should return the flavors as an array.", (done) => {
44
+      const token = 'test_token';
45
+
46
+      fetchMock.mock(mockData.flavorList(token));
47
+      nova
48
+        .flavorList(token)
49
+        .then((images) => {
50
+          expect(images.length).not.toBe(0);
51
+          done();
52
+        })
53
+        .catch((error) => done.fail(error));
54
+    });
55
+
56
+    it("Should not cache its results", (done) => {
57
+      const token = 'test_token';
58
+
59
+      let mockOptions = mockData.flavorList(token);
60
+      fetchMock.mock(mockOptions);
61
+
62
+      nova
63
+        .flavorList(token)
64
+        .then(() => {
65
+          expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1);
66
+          return nova.flavorList(token);
67
+        })
68
+        .then(() => {
69
+          expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2);
70
+          done();
71
+        })
72
+        .catch((error) => done.fail(error));
73
+    });
74
+  });
75
+});

+ 4
- 0
vagrant.sh View File

@@ -54,6 +54,10 @@ allowed_origin=http://localhost:9876
54 54
 [[post-config|\$NEUTRON_CONF]]
55 55
 [cors]
56 56
 allowed_origin=http://localhost:9876
57
+
58
+[[post-config|\$NOVA_CONF]]
59
+[cors]
60
+allowed_origin=http://localhost:9876
57 61
 EOL
58 62
 
59 63
 # Start devstack.

Loading…
Cancel
Save