From c330bbdc62633da357f68540a6db57dbed68942d Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Wed, 5 Oct 2016 18:32:20 -0400 Subject: [PATCH] Added networkList() method to OpenStack wrapper This patch adds the networkList() method to the OpenStack wrapper class. This method will initialize `Neutron` instance if needed and cache it. As neutron needs the URL, `Keystone` instance is also initialize and cache if needed. Change-Id: If12af29a86b9856a145c1f91de25eb3473938de5 --- src/openstack.js | 70 +++++++++++++++++++ src/util/abstractService.js | 9 +++ test/functional/openstackTest.js | 23 +++++++ test/unit/openstackTest.js | 114 ++++++++++++++++++++++++++++--- 4 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 test/functional/openstackTest.js diff --git a/src/openstack.js b/src/openstack.js index 8cbf5e0..5df6332 100644 --- a/src/openstack.js +++ b/src/openstack.js @@ -1,3 +1,6 @@ +import Keystone from "./keystone"; +import Neutron from "./neutron"; + export default class OpenStack { /** * Create wrapper class that takes clouds.yaml instance @@ -15,8 +18,75 @@ export default class OpenStack { this.cloudConfig = cloudConfig; } + getConfig() { // Returns the config instance return this.cloudConfig; } + + /** + * List the networks available. + * + * @returns {Promise.} A promise which will resolve with the list of networks. + */ + networkList() { + return this._neutron + .then((neutron) => neutron.networkList(this._token)); + } + + /** + * Keystone component. + * + * @returns {Promise.} A promise which will resolve with Keystone instance. + * @private + */ + get _keystone() { + if (!this._keystonePromise) { + this._keystonePromise = Promise.resolve(new Keystone(this.getConfig())); + } + + return this._keystonePromise; + } + + /** + * Neutron component. + * + * @returns {Promise.} A promise which will resolve with Neutron instance. + * @private + */ + get _neutron() { + if (!this._neutronPromise) { + this._neutronPromise = this._getComponentConfigFor('neutron') + .then((componentConfig) => new Neutron(componentConfig)); + } + return this._neutronPromise; + } + + /** + * Token issued from Keystone. + * + * @returns {Promise.} A promise which will resolve with the token. + * @private + */ + get _token() { + if (!this._tokenPromise) { + this._tokenPromise = this._keystone.then((k) => k.tokenIssue()); + } + return this._tokenPromise; + } + + /** + * Return an component config from keystone catalog. + * + * @param {String} name A component name to find. + * @returns {Promise.<{}>} A promise which will resolve with the component config. + * @private + */ + _getComponentConfigFor(name) { + return this._token + .then((token) => this._keystone.then((keystone) => keystone.catalogList(token))) + .then((catalog) => catalog.find((entry) => entry.name === name)) + .then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public')); + } + } diff --git a/src/util/abstractService.js b/src/util/abstractService.js index 0a1654d..517f9e1 100644 --- a/src/util/abstractService.js +++ b/src/util/abstractService.js @@ -52,6 +52,15 @@ export default class AbstractService { return this._supportedVersions || []; } + /** + * Our endpoint URL for this service. + * + * @returns {string} The URL of our service. + */ + get endpointUrl() { + return this._endpointUrl; + } + /** * Retrieve all the API versions available. * diff --git a/test/functional/openstackTest.js b/test/functional/openstackTest.js new file mode 100644 index 0000000..c1ac912 --- /dev/null +++ b/test/functional/openstackTest.js @@ -0,0 +1,23 @@ +import config from "./helpers/cloudsConfig"; +import OpenStack from "../../src/openstack"; +import log from 'loglevel'; + +log.setLevel("DEBUG"); + +describe("OpenStack", () => { + let devstackConfig = config.clouds.devstack; + + describe("networkList()", () => { + it("should return the networks as an array.", (done) => { + const openstack = new OpenStack(devstackConfig); + + openstack.networkList() + .then((networks) => { + expect(networks.length > 0).toBeTruthy(); + done(); + }) + .catch((error) => done.fail(error)); + }); + }); + +}); diff --git a/test/unit/openstackTest.js b/test/unit/openstackTest.js index 5a4b886..4b349ca 100644 --- a/test/unit/openstackTest.js +++ b/test/unit/openstackTest.js @@ -1,16 +1,17 @@ import OpenStack from "../../src/openstack"; -import * as mockData from './helpers/data/openstack'; - -const FetchMock = require('fetch-mock'); +import * as openStackMockData from './helpers/data/openstack'; +import * as neutronMockData from './helpers/data/neutron'; +import * as keystoneMockData from './helpers/data/keystone'; +import fetchMock from 'fetch-mock'; +import Neutron from "../../src/neutron"; +import Keystone from "../../src/keystone"; describe("Simple test", () => { - afterEach(() => { - FetchMock.reset(); - }); + afterEach(fetchMock.restore); it("should export a class", () => { - let t = new OpenStack(mockData.config); + let t = new OpenStack(openStackMockData.config); expect(t).toBeDefined(); }); @@ -24,9 +25,106 @@ describe("Simple test", () => { }); it("getConfig should returns the config", () => { - let openstack = new OpenStack(mockData.config); + let openstack = new OpenStack(openStackMockData.config); let config = openstack.getConfig(); expect(config.region_name).toEqual('Region1'); }); + describe('networkList', () => { + it('should fetch networkList from neutron', (done) => { + const openstack = new OpenStack(openStackMockData.config); + const neutron = mockNeutron(openstack); + const networksData = neutronMockData.networkList('token').response.networks; + + spyOn(neutron, 'networkList').and.returnValue(Promise.resolve(networksData)); + + openstack.networkList() + .then((networks) => { + expect(networks.length).toBe(2); + done(); + }) + .catch((error) => done.fail(error)); + }); + }); + + describe('_neutron', () => { + it('creates Neutron instance with the correct endpoint', (done) => { + const token = 'test_token'; + const openstack = new OpenStack(openStackMockData.config); + const keystone = mockKeystone(openstack); + const catalogData = keystoneMockData.catalogList(token).response.catalog; + + spyOn(keystone, 'tokenIssue').and.returnValue(Promise.resolve(token)); + spyOn(keystone, 'catalogList').and.returnValue(Promise.resolve(catalogData)); + + openstack._neutron + .then((neutron) => { + expect(keystone.catalogList).toHaveBeenCalledWith(token); + expect(neutron).toEqual(jasmine.any(Neutron)); + expect(neutron.endpointUrl).toEqual('http://192.168.99.99:9696/'); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it('should cache Neutron instance and Keystone token', (done) => { + const openstack = new OpenStack(openStackMockData.config); + const tokenIssueMock = keystoneMockData.tokenIssue(); + const catalogListMock = keystoneMockData.catalogList('test_token'); + + fetchMock.mock(keystoneMockData.root()); + fetchMock.mock(tokenIssueMock); + fetchMock.mock(catalogListMock); + + openstack._neutron + .then((neutron) => { + expect(neutron).toEqual(jasmine.any(Neutron)); + expect(fetchMock.calls(tokenIssueMock.matcher).length).toEqual(1); + expect(fetchMock.calls(catalogListMock.matcher).length).toEqual(1); + return openstack._neutron; + }) + .then((neutron) => { + expect(neutron).toEqual(jasmine.any(Neutron)); + expect(fetchMock.calls(tokenIssueMock.matcher).length).toEqual(1); + expect(fetchMock.calls(catalogListMock.matcher).length).toEqual(1); + done(); + }) + .catch((error) => done.fail(error)); + }); + }); + + describe('_token', () => { + it('should fetch the token and cache it', (done) => { + const openstack = new OpenStack(openStackMockData.config); + const keystone = mockKeystone(openstack); + + spyOn(keystone, 'tokenIssue').and.returnValue(Promise.resolve('test_token')); + + openstack._token + .then((token) => { + expect(token).toEqual('test_token'); + expect(keystone.tokenIssue.calls.count()).toEqual(1); + return openstack._token; + }) + .then((token) => { + expect(token).toEqual('test_token'); + expect(keystone.tokenIssue.calls.count()).toEqual(1); + done(); + }) + .catch((error) => done.fail(error)); + }); + }); + + function mockKeystone(openstack) { + const keystone = new Keystone(keystoneMockData.config); + openstack._keystonePromise = Promise.resolve(keystone); + return keystone; + } + + function mockNeutron(openstack) { + const neutron = new Neutron(neutronMockData.config); + openstack._neutronPromise = Promise.resolve(neutron); + return neutron; + } + });