Use serviceEndpoint compatible with versions

This patch adds a more flexible selection of version in AbstractService.
A version will be selected if it's compatible with the 'supportedVersions'.
Example:
  supportedVersions = ['v3.1'];
  All minor versions above 3.1 will be compatible (3.1.2, 3.2, 3.3 etc)
  However 4.x will not be compatible

Change-Id: Icd540449ebf6a09d9bb7e1d25a85e2dbe787c5a4
This commit is contained in:
Corentin Ardeois 2016-11-22 13:51:46 -05:00
parent cffe4a7c94
commit beec17c6e4
12 changed files with 176 additions and 125 deletions

View File

@ -7,7 +7,7 @@ import AbstractService from './util/abstractService';
* @ignore
*/
const supportedKeystoneVersions = [
'v3.7'
'v3.1'
];
export default class Keystone extends AbstractService {
@ -59,12 +59,13 @@ export default class Keystone extends AbstractService {
}
/**
* Retrieve all the API versions available.
* Retrieve all the raw API versions available.
*
* @returns {Promise.<T>} A promise that will resolve with the list of API versions.
* @returns {Promise.<Object[]>} A promise that will resolve with the list of raw versions.
* @protected
*/
versions() {
return super.versions()
_rawVersions() {
return super._rawVersions()
.then((versions) => versions.values);
}

View File

@ -15,6 +15,7 @@
*/
import Http from './http';
import Version from './version';
import URL from 'url-parse';
export default class AbstractService {
@ -64,9 +65,25 @@ export default class AbstractService {
/**
* Retrieve all the API versions available.
*
* @returns {Promise.<T>} A promise that will resolve with the list of API versions.
* @returns {Promise.<Version[]>} A promise that will resolve with the list of API versions.
*/
versions() {
return this._rawVersions().then((versions) => {
return versions.map((rawVersion) => {
const version = new Version(rawVersion.id);
version.links = rawVersion.links;
return version;
});
});
}
/**
* Retrieve all the raw API versions available.
*
* @returns {Promise.<Object[]>} A promise that will resolve with the list of raw versions.
* @protected
*/
_rawVersions() {
return new Promise((resolve, reject) => {
let promise = this.http
.httpGet(this._endpointUrl)
@ -92,14 +109,14 @@ export default class AbstractService {
/**
* Retrieve the API version declaration that is currently in use by this instance.
*
* @returns {Promise.<T>} A promise that will resolve with the specific API version.
* @returns {Promise.<Version>} A promise that will resolve with the specific API version.
*/
version() {
return this
.versions()
.then((versions) => {
for (let version of versions) {
if (this.supportedVersions.indexOf(version.id) > -1) {
if (this.supportedVersions.find(version.supports)) {
return version;
}
}

View File

@ -56,10 +56,30 @@ export default class Version {
return this._patch || 0;
}
/**
* The links of the service
*
* @returns {Object[]} The list of links.
*/
get links() {
return this._links || null;
}
/**
* Sets the links of the service
*
* @param {Object[]} links The links to be set
*/
set links(links) {
if (Array.isArray(links)) {
this._links = links;
}
}
/**
* Create a new instance of a service version.
*
* @param {String} service The name of the service.
* @param {String} [service] The name of the service.
* @param {String} versionString The version string for this service.
*/
constructor(service, versionString) {
@ -89,6 +109,10 @@ export default class Version {
this._minor = parseInt(results[6], 10);
this._patch = parseInt(results[8], 10);
}
this._links = null;
this.equals = this.equals.bind(this);
this.supports = this.supports.bind(this);
}
/**
@ -112,4 +136,38 @@ export default class Version {
version.patch === this.patch &&
version.service === this.service;
}
/**
* Verifies compatibility of this instance to another instance. Major version should be equal and
* minor version should be greater or equal than `version` parameter.
*
* @param {String|Version} version the version to support.
* @returns {boolean} True if the version is compatible, otherwise false
*/
supports(version) {
if (!(version instanceof Version)) {
if (typeof version === 'string') {
version = new Version(version);
} else {
return false;
}
}
const compatibleVersion = version.service === this.service &&
version.major === this.major &&
version.minor <= this.minor;
if (compatibleVersion && version.minor === this.minor) {
return version.patch <= this.patch;
}
return compatibleVersion;
}
toString() {
let version = `${this.major}.${this.minor}`;
if (this.patch) {
version = `${version}.${this.patch}`;
}
return version;
}
}

View File

@ -50,7 +50,7 @@ describe("Glance", () => {
describe("version()", () => {
const supportedApiVersions = [
new Version('image 2.3')
new Version('2.4')
];
/**
@ -61,11 +61,7 @@ describe("Glance", () => {
configPromise
.then((config) => new Glance(config))
.then((glance) => glance.version())
.then((version) => {
// Quick sanity check.
const apiVersion = new Version('image', version.id);
.then((apiVersion) => {
for (let i = 0; i < supportedApiVersions.length; i++) {
let supportedVersion = supportedApiVersions[i];
if (apiVersion.equals(supportedVersion)) {
@ -73,7 +69,7 @@ describe("Glance", () => {
return;
}
}
fail("Current devstack glance version is not supported.");
fail(`Current devstack glance version (${apiVersion}) is not supported.`);
done();
})
.catch((error) => done.fail(error));

View File

@ -43,7 +43,7 @@ describe("Keystone", () => {
describe("version()", () => {
const supportedApiVersions = [
new Version('identity 3.7')
new Version('3.7')
];
/**
@ -52,11 +52,7 @@ describe("Keystone", () => {
*/
it("should return a supported version.", (done) => {
keystone.version()
.then((version) => {
// Quick sanity check.
const apiVersion = new Version('identity', version.id);
.then((apiVersion) => {
for (let i = 0; i < supportedApiVersions.length; i++) {
let supportedVersion = supportedApiVersions[i];
if (apiVersion.equals(supportedVersion)) {
@ -64,7 +60,7 @@ describe("Keystone", () => {
return;
}
}
fail("Current devstack keystone version is not supported.");
fail(`Current devstack keystone version (${apiVersion}) is not supported.`);
done();
})
.catch((response) => response.json()

View File

@ -50,7 +50,7 @@ describe("neutron", () => {
describe("version()", () => {
const supportedApiVersions = [
new Version('network 2.0')
new Version('2.0')
];
/**
@ -61,11 +61,7 @@ describe("neutron", () => {
configPromise
.then((config) => new Neutron(config))
.then((neutron) => neutron.version())
.then((version) => {
// Quick sanity check.
const apiVersion = new Version('network', version.id);
.then((apiVersion) => {
for (let i = 0; i < supportedApiVersions.length; i++) {
let supportedVersion = supportedApiVersions[i];
if (apiVersion.equals(supportedVersion)) {
@ -73,7 +69,7 @@ describe("neutron", () => {
return;
}
}
fail("Current devstack neutron version is not supported.");
fail(`Current devstack neutron version (${apiVersion}) is not supported.`);
done();
})
.catch((error) => done.fail(error));

View File

@ -31,21 +31,6 @@ describe('Glance', () => {
expect(() => new Glance()).toThrow();
});
describe("version()", () => {
it("Should return a supported version of the glance API.", (done) => {
const glance = new Glance(mockData.config);
fetchMock.mock(mockData.root());
glance.version()
.then((version) => {
expect(version.id).toEqual('v2.3');
done();
})
.catch((error) => done.fail(error));
});
});
describe("serviceEndpoint()", () => {
it("Should return a valid endpoint to the glance API.", (done) => {
const glance = new Glance(mockData.config);

View File

@ -75,16 +75,6 @@ function rootResponse() {
}
]
},
{
status: "SUPPORTED",
id: "v2.0",
links: [
{
href: `${rootUrl}v2/`,
rel: "self"
}
]
},
{
status: "SUPPORTED",
id: "v1.1",

View File

@ -15,43 +15,6 @@ describe('Keystone', () => {
expect(() => new Keystone()).toThrow();
});
describe("versions()", () => {
/**
* Keystone needs an explicit test, as it uses a slightly different data format
* than other services.
*/
it("Should return a list of all versions available on this clouds' keystone", (done) => {
const keystone = new Keystone(mockData.config);
fetchMock.mock(mockData.root());
keystone.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(2);
done();
})
.catch((error) => done.fail(error));
});
});
describe("version()", () => {
it("Should return a supported version of the keystone API.", (done) => {
const keystone = new Keystone(mockData.config);
fetchMock.mock(mockData.root());
keystone.version()
.then((version) => {
expect(version.id).toEqual('v3.7');
done();
})
.catch((error) => done.fail(error));
});
});
describe("serviceEndpoint()", () => {
it("Should return a valid endpoint to the keystone API.", (done) => {
const keystone = new Keystone(mockData.config);

View File

@ -36,22 +36,6 @@ describe('neutron', () => {
}
});
describe("versions()", () => {
it("Should return a list of all versions available on this clouds' NEUTRON", (done) => {
const neutron = new Neutron(mockData.config);
fetchMock.mock(mockData.root());
neutron.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(1);
done();
})
.catch((error) => done.fail(error));
});
});
describe("networkList()", () => {
let neutron = null;

View File

@ -53,21 +53,12 @@ describe('AbstractService', () => {
service.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(6);
done();
})
.catch((error) => done.fail(error));
});
it("Should return a list of all versions available from this resource", (done) => {
const service = new AbstractService(mockData.rootUrl, mockData.versions);
fetchMock.mock(mockData.rootResponse());
service.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(6);
expect(versions.length).toBe(5);
expect(versions[0].major).toEqual(2);
expect(versions[0].minor).toEqual(3);
expect(versions[0].patch).toEqual(0);
expect(versions[0].links).not.toBe(null);
expect(versions[0].links[0].href).toEqual('http://example.com/v2/');
done();
})
.catch((error) => done.fail(error));
@ -84,7 +75,7 @@ describe('AbstractService', () => {
service.versions()
.then((versions) => {
// Quick sanity check.
expect(versions.length).toBe(6);
expect(versions.length).toBe(5);
done();
})
.catch((error) => done.fail(error));
@ -132,7 +123,22 @@ describe('AbstractService', () => {
service.version()
.then((version) => {
expect(version.id).toEqual('v2.3');
expect(version.equals('v2.3')).toBe(true);
done();
})
.catch((error) => done.fail(error));
});
it("Should return the latest compatible version of the service API.", (done) => {
const service = new AbstractService(mockData.rootUrl, [
'v2.0'
]);
fetchMock.mock(mockData.rootResponse());
service.version()
.then((version) => {
expect(version.equals('v2.3')).toBe(true);
done();
})
.catch((error) => done.fail(error));

View File

@ -96,4 +96,63 @@ describe('Version', () => {
// Other tests...
expect(v2.equals({})).toBe(false);
});
it("should test for correct compatibility", () => {
const v1 = new Version("compute", "1.3.2");
// String tests
expect(v1.supports("compute 1.0.0")).toBe(true);
expect(v1.supports("compute 1.0.1")).toBe(true);
expect(v1.supports("compute 1.3.0")).toBe(true);
expect(v1.supports("compute 1.3.3")).toBe(false);
expect(v1.supports("compute 1.4.0")).toBe(false);
expect(v1.supports("compute 2.3.0")).toBe(false);
// Version tests
expect(v1.supports(new Version("compute", "1.0.0"))).toBe(true);
expect(v1.supports(new Version("compute", "1.0.1"))).toBe(true);
expect(v1.supports(new Version("compute", "1.3.0"))).toBe(true);
expect(v1.supports(new Version("compute", "1.3.3"))).toBe(false);
expect(v1.supports(new Version("compute", "1.4.0"))).toBe(false);
expect(v1.supports(new Version("compute", "2.3.0"))).toBe(false);
const v2 = new Version("1.3.2");
// String tests
expect(v2.supports("1.0.0")).toBe(true);
expect(v2.supports("1.0.1")).toBe(true);
expect(v2.supports("1.3.0")).toBe(true);
expect(v2.supports("1.3.3")).toBe(false);
expect(v2.supports("1.4.0")).toBe(false);
expect(v2.supports("2.3.0")).toBe(false);
// Version tests
expect(v2.supports(new Version("1.0.0"))).toBe(true);
expect(v2.supports(new Version("1.0.1"))).toBe(true);
expect(v2.supports(new Version("1.3.0"))).toBe(true);
expect(v2.supports(new Version("1.3.3"))).toBe(false);
expect(v2.supports(new Version("1.4.0"))).toBe(false);
expect(v2.supports(new Version("2.3.0"))).toBe(false);
});
it("should store links", () => {
const v1 = new Version("compute", "1.3.2");
expect(v1.links).toBe(null);
v1.links = 'wrong data';
expect(v1.links).toBe(null);
v1.links = [
{
href: `http://example.org/v2/`,
rel: "self"
}
];
expect(v1.links).not.toBe(null);
expect(v1.links.length).toBe(1);
expect(v1.links[0]).toEqual({
href: "http://example.org/v2/",
rel: "self"
});
});
});