retire js-openstack-lib
This project is being retired Release update: https://review.opendev.org/c/openstack/governance/+/771789 ML: http://lists.openstack.org/pipermail/openstack-discuss/2021-January/019847.html Codesearch: https://codesearch.opendev.org/?q=js-openstack-lib&i=nope&files=&excludeFiles=&repos= Change-Id: I94c42c0ac3ec23c685d1b50d35383ad078916f06
This commit is contained in:
parent
346f7eeda0
commit
0a47bb9d26
174
LICENSE
174
LICENSE
|
@ -1,174 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
67
README.rst
67
README.rst
|
@ -1,67 +0,0 @@
|
|||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
|
||||
.. image:: http://governance.openstack.org/badges/js-openstack-lib.svg
|
||||
:target: http://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
JS-OpenStack-lib
|
||||
================
|
||||
|
||||
JS-OpenStack-lib is a Javascript library for interacting with OpenStack clouds. The project aims to provide a consistent and complete set of interactions with OpenStack's many services, along with documentations, examples, and tools.
|
||||
This library is compatible with both browser and server side Javascript.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The following example simply connects to an OpenStack cloud and list flavors in the Compute service:
|
||||
|
||||
::
|
||||
|
||||
import OpenStack from 'js-openstack-lib';
|
||||
|
||||
// Initialize cloud
|
||||
// cloudConfig is a JSON object corresponding to clouds.yaml
|
||||
// (It is your responsibility to load and parse it)
|
||||
const openStack = new OpenStack({
|
||||
region_name: 'Region1',
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
project_name: 'js-openstack-lib',
|
||||
auth_url: 'http://192.168.99.99/'
|
||||
}
|
||||
});
|
||||
// List all flavors
|
||||
openStack.networkList()
|
||||
.then((networks) => {
|
||||
console.log(networks);
|
||||
});
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Coming soon
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you're interested in contributing, the following will help you get started:
|
||||
|
||||
:Bug Tracker: https://storyboard.openstack.org/#!/project/844
|
||||
:Code Hosting: https://git.openstack.org/cgit/openstack/js-openstack-lib
|
||||
:Code Review:
|
||||
https://review.openstack.org/#/q/status:open+project:openstack/js-openstack-lib,n,z
|
||||
|
||||
Please read `Developer's Guide <http://docs.openstack.org/infra/manual/developers.html>`_ before sending your first patch for review
|
||||
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Apache 2.0
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.network 'private_network', ip: '192.168.99.99'
|
||||
config.vm.hostname = 'devstack'
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = "4096"
|
||||
end
|
||||
|
||||
config.vm.provision "shell", path: "vagrant.sh"
|
||||
end
|
42
bindep.txt
42
bindep.txt
|
@ -1,42 +0,0 @@
|
|||
# This is a cross-platform list tracking distribution packages needed by tests;
|
||||
# see http://docs.openstack.org/infra/bindep/ for additional information.
|
||||
|
||||
apt-transport-https
|
||||
lsb-release
|
||||
ant
|
||||
build-essential [platform:dpkg]
|
||||
curl
|
||||
cyrus-sasl-devel [platform:rpm]
|
||||
dbus
|
||||
firefox [!platform:debian]
|
||||
gawk
|
||||
iceweasel [platform:debian]
|
||||
language-pack-en [platform:ubuntu]
|
||||
libcurl-devel [platform:rpm]
|
||||
libcurl4-gnutls-dev [platform:dpkg]
|
||||
libffi-dev [platform:dpkg]
|
||||
libffi-devel [platform:rpm]
|
||||
libjpeg-dev [platform:dpkg]
|
||||
libjpeg-turbo-devel [platform:rpm]
|
||||
libldap2-dev [platform:dpkg]
|
||||
libsasl2-dev [platform:dpkg]
|
||||
libselinux-python [platform:rpm]
|
||||
libuuid-devel [platform:rpm]
|
||||
locales [platform:debian]
|
||||
pkg-config [platform:dpkg]
|
||||
pkgconfig [platform:rpm]
|
||||
python-dev [platform:dpkg]
|
||||
python-devel [platform:rpm]
|
||||
python-libvirt [platform:dpkg]
|
||||
python-lxml
|
||||
python-sphinx [platform:dpkg]
|
||||
python-openstackdocstheme [platform:dpkg]
|
||||
python3-all-dev [platform:ubuntu-trusty]
|
||||
python3-dev [platform:dpkg]
|
||||
python3-devel [platform:fedora]
|
||||
python3.4 [platform:ubuntu-trusty]
|
||||
python34-devel [platform:centos]
|
||||
unzip
|
||||
uuid-dev [platform:dpkg]
|
||||
xorg-x11-server-Xvfb [platform:rpm]
|
||||
xvfb [platform:dpkg]
|
|
@ -1,26 +0,0 @@
|
|||
/* eslint no-process-env: "off" */
|
||||
import fs from 'fs'
|
||||
import karma from 'karma/lib/config'
|
||||
import path from 'path'
|
||||
|
||||
function getDevstackConfig () {
|
||||
const karmaConfig = karma.parseConfig(path.resolve('./karma.conf.js'))
|
||||
|
||||
return getCorsConfig('$KEYSTONE_CONF', karmaConfig) +
|
||||
getCorsConfig('$GLANCE_API_CONF', karmaConfig) +
|
||||
getCorsConfig('$NEUTRON_CONF', karmaConfig) +
|
||||
getCorsConfig('$NOVA_CONF', karmaConfig)
|
||||
}
|
||||
|
||||
function getCorsConfig (service, karmaConfig) {
|
||||
return `[[post-config|${service}]]
|
||||
[cors]
|
||||
allowed_origin=http://localhost:${karmaConfig.port}
|
||||
`
|
||||
}
|
||||
|
||||
fs.appendFile(process.env.BASE + '/new/devstack/local.conf', getDevstackConfig(), (err) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
227
doc/Makefile
227
doc/Makefile
|
@ -1,227 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
VENVDIR = .venv
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " epub3 to make an epub3"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
@echo " dummy to check syntax errors of document sources"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.PHONY: dirhtml
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
.PHONY: singlehtml
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
.PHONY: pickle
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
.PHONY: json
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
.PHONY: htmlhelp
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
.PHONY: qthelp
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/openstack-lib.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/openstack-lib.qhc"
|
||||
|
||||
.PHONY: applehelp
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
.PHONY: devhelp
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/openstack-lib"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/openstack-lib"
|
||||
@echo "# devhelp"
|
||||
|
||||
.PHONY: epub
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
.PHONY: epub3
|
||||
epub3:
|
||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||
@echo
|
||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||
|
||||
.PHONY: latex
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
.PHONY: latexpdf
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: latexpdfja
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: text
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
.PHONY: man
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
.PHONY: texinfo
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
.PHONY: gettext
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
.PHONY: changes
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
.PHONY: linkcheck
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
.PHONY: doctest
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
.PHONY: xml
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
.PHONY: pseudoxml
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
.PHONY: dummy
|
||||
dummy:
|
||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||
@echo
|
||||
@echo "Build finished. Dummy builder generates no files."
|
|
@ -1,107 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
import openstackdocstheme
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc'
|
||||
]
|
||||
|
||||
exclude_patterns = [
|
||||
'template.rst',
|
||||
]
|
||||
|
||||
# Optionally allow the use of sphinxcontrib.spelling to verify the
|
||||
# spelling of the documents.
|
||||
try:
|
||||
import sphinxcontrib.spelling
|
||||
extensions.append('sphinxcontrib.spelling')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'js-openstack-lib'
|
||||
copyright = u'%s, OpenStack Foundation' % datetime.date.today().year
|
||||
|
||||
# A few variables have to be set for the log-a-bug feature.
|
||||
# giturl: The location of conf.py on Git. Must be set manually.
|
||||
# gitsha: The SHA checksum of the bug description. Extracted from git log.
|
||||
# bug_tag: Tag for categorizing the bug. Must be set manually.
|
||||
# bug_project: Launchpad project to file bugs against.
|
||||
# These variables are passed to the logabug code via html_context.
|
||||
giturl = u'https://git.openstack.org/cgit/openstack/js-openstack-lib/tree/doc/source'
|
||||
git_cmd = "/usr/bin/git log | head -n1 | cut -f2 -d' '"
|
||||
gitsha = os.popen(git_cmd).read().strip('\n')
|
||||
bug_tag = "docs"
|
||||
# source tree
|
||||
pwd = os.getcwd()
|
||||
# html_context allows us to pass arbitrary values into the html template
|
||||
html_context = {"pwd": pwd,
|
||||
"gitsha": gitsha,
|
||||
"bug_tag": bug_tag,
|
||||
"giturl": giturl}
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = [openstackdocstheme.get_html_theme_path()]
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -1,17 +0,0 @@
|
|||
================================
|
||||
Contributing to js-openstack-lib
|
||||
================================
|
||||
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow section
|
||||
of this documentation to learn how changes to OpenStack should be submitted for
|
||||
review via the Gerrit tool:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
More TBD.
|
|
@ -1,5 +0,0 @@
|
|||
===============
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
This section TBD.
|
|
@ -1,25 +0,0 @@
|
|||
========================
|
||||
OpenStack JavaScript SDK
|
||||
========================
|
||||
|
||||
Welcome to the OpenStack JavaScript SDK. This SDK is still under
|
||||
construction, and many design decisions are being made. If you'd like to be
|
||||
involved in this process, please take a look at the specifications and
|
||||
patches under discussion in gerrit_, or come visit us in
|
||||
`#openstack-javascript`_ on FreeNode (IRC).
|
||||
|
||||
Topics
|
||||
======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
getting_started
|
||||
reference_documentation
|
||||
dev_get_started
|
||||
specs
|
||||
releasing
|
||||
|
||||
|
||||
.. _gerrit: https://review.openstack.org/#/q/status:open+AND+project:openstack/js-openstack-lib,n,z
|
||||
.. _#openstack-javascript: http://webchat.freenode.net/?channels=openstack-javascript
|
|
@ -1,11 +0,0 @@
|
|||
=======================
|
||||
Reference Documentation
|
||||
=======================
|
||||
|
||||
Below you will find reference documentation for individual components of the JavaScript SDK. For
|
||||
detailed, method-specific documentation, select the appropriate method below.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
jsdoc/Keystone
|
|
@ -1,26 +0,0 @@
|
|||
===============================
|
||||
How to release js-openstack-lib
|
||||
===============================
|
||||
|
||||
Only follow these steps if you have authority to release a version of js-openstack-lib.
|
||||
|
||||
1. Ensure that you have gpg set up locally.
|
||||
If you do not currently have gpg installed:
|
||||
|
||||
`brew install gpg gpg2`
|
||||
|
||||
Set up your gpg key:
|
||||
|
||||
https://wiki.openstack.org/wiki/Oslo/ReleaseProcess#Setting_Up_GPG
|
||||
|
||||
2. `git pull --ff-only`
|
||||
|
||||
3. `git tag -s <version number>`
|
||||
|
||||
4. `git push gerrit <version number>`
|
||||
|
||||
Git won’t have a remote named gerrit until the first time git-review runs.
|
||||
You may need to run git review -s before the push.
|
||||
|
||||
For more information see:
|
||||
http://docs.openstack.org/infra/manual/drivers.html#tagging-a-release
|
|
@ -1,47 +0,0 @@
|
|||
=====================
|
||||
Design Specifications
|
||||
=====================
|
||||
|
||||
Priority Specifications
|
||||
=======================
|
||||
|
||||
This is a list of approved design specifications, which are considered
|
||||
high-priority for this project. It is very likely that work is already in
|
||||
progress, however contributions and reviews are always welcome.
|
||||
|
||||
.. If you add additional projects to this list, please update the gerrit
|
||||
query block.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
|
||||
Gerrit query for all changes related to priority efforts::
|
||||
|
||||
status:open AND project:openstack/js-openstack-lib
|
||||
|
||||
https://review.openstack.org/#/q/status:open+AND+project:openstack/js-openstack-lib,n,z
|
||||
|
||||
Approved Specifications
|
||||
=======================
|
||||
|
||||
These are specifications that have been approved; work may or may not
|
||||
have started on these. Reviewers will review related changes as time
|
||||
permits.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
specs/devstack
|
||||
specs/es2015.rst
|
||||
|
||||
|
||||
Implemented Specifications
|
||||
==========================
|
||||
|
||||
These specifications have already been implemented and are listed here
|
||||
for historical purposes.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
|
@ -1,95 +0,0 @@
|
|||
::
|
||||
|
||||
Copyright 2016 Hewlett Packard Development Corporation, L.P.
|
||||
|
||||
This work is licensed under a Creative Commons Attribution 3.0
|
||||
Unported License.
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
..
|
||||
|
||||
|
||||
===================================
|
||||
DevStack and Third-Party CI Systems
|
||||
===================================
|
||||
|
||||
This spec attempts to outline the various requirements regarding testing
|
||||
against upstream OpenStack, and accepting feedback from downstream
|
||||
consumers of our library.
|
||||
|
||||
Problem Description
|
||||
===================
|
||||
|
||||
We are, effectively, a downstream consumer of the OpenStack API's, which
|
||||
means that we're effectively building on quicksand. Even after a release has
|
||||
been published, backports can break any integration tests that we write.
|
||||
While we can't force the upstream projects to confirm that they remain
|
||||
backwards compatible, we can nevertheless ensure that we are notified as
|
||||
quickly as possible when something breaks.
|
||||
|
||||
Furthermore, it is very likely that OpenStack deployers, such as Rackspace,
|
||||
Mirantis, HPE, or others, will build their own developer tooling on top of
|
||||
ours, creating the potential for cascading gate failures.
|
||||
|
||||
Proposed Change
|
||||
===============
|
||||
|
||||
We will create a devstack gate job, run against every patch. Furthermore, we
|
||||
will encourage any third-party consumers of our libraries to run similar jobs
|
||||
against our patches.
|
||||
|
||||
Existing gate jobs will be used as a template. Third Party CI jobs will be
|
||||
asked to implement the Infra documentation: `Third Party CI`_.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Primary assignee:
|
||||
TBD
|
||||
|
||||
Gerrit Topic
|
||||
------------
|
||||
|
||||
Use Gerrit topic "js-devstack" for all patches related to this spec.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git-review -t js-devstack
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
1. Create a test configuration which will run our test suite against a
|
||||
provided devstack catalog.
|
||||
2. Create an npm script target which runs this test suite.
|
||||
3. Create an infra job which executes the aforementioned npm script target.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Additional documentation will need to be added to this project's Developer
|
||||
Guidelines, providing a guide on how devstack jobs can be run by individual
|
||||
contributors.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
No security risks.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
This project will need at least one test that can be run against devstack.
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
- We need a way of configuring and/or discovering a cloud's endpoints.
|
||||
- We need to agree on a testing framework, and whether we're targeting Node
|
||||
or Browser apps.
|
||||
- We need minimal functionality (recommending keystone) that can be tested.
|
||||
|
||||
.. _`Third Party CI`: http://docs.openstack.org/infra/system-config/third_party.html
|
|
@ -1,187 +0,0 @@
|
|||
::
|
||||
|
||||
Copyright 2016 Hewlett Packard Development Corporation, L.P.
|
||||
|
||||
This work is licensed under a Creative Commons Attribution 3.0
|
||||
Unported License.
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
..
|
||||
|
||||
===========================
|
||||
Support for ECMAScript 2015
|
||||
===========================
|
||||
|
||||
We need to make a decision on what language this project will use, and
|
||||
whether we will transpile to other languages. If we want to use a transpiler,
|
||||
we need to support it both for Node.js and browser builds and tests.
|
||||
|
||||
Problem Description
|
||||
===================
|
||||
|
||||
There are four languages currently in active use in the JavaScript community.
|
||||
ECMAScript 2015, ECMAScript 5, CoffeeScript, and TypeScript. It also needs to
|
||||
support multiple runtimes: Browser and Node.js. We need to settle on which
|
||||
language to use, and how to support all of our targeted runtimes.
|
||||
|
||||
Proposed Change
|
||||
===============
|
||||
|
||||
This project will use ECMAScript 2015 as its primary language. Support for
|
||||
projects that do not support this language will be provided using Babel as
|
||||
a transpiler.
|
||||
|
||||
Setting up Babel
|
||||
----------------
|
||||
|
||||
Babel should be configured using `.babelrc` file, so that configuration will
|
||||
be used for all the environments we're targeting.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"presets": ["es2015"]
|
||||
}
|
||||
|
||||
Node.js Build
|
||||
-------------
|
||||
|
||||
We're targeting Node.js v4, which doesn't support all the ES2015 features, so
|
||||
usage of Babel is required. Babel can be integrated by specifying `prepublish`
|
||||
hook which runs Babel to transpile library's code and overriding main module in
|
||||
`package.json`.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"main": "./dist/index.js",
|
||||
"scripts": {
|
||||
"build": "babel ./src --out-dir ./dist",
|
||||
"prepublish": "npm run build"
|
||||
}
|
||||
}
|
||||
|
||||
Browser Build
|
||||
-------------
|
||||
|
||||
Webpack will be used to create ES5 builds which are intended to run in
|
||||
browsers. `babel-loader` should be used to integrate Webpack and Babel.
|
||||
|
||||
Since `node-fetch` module which is used for sending HTTP requests isn't
|
||||
supposed to be run in browser environments, it should be substituted with
|
||||
`whatwg-fetch` polyfill using Webpack's `node` configuration option.
|
||||
|
||||
Here is an example of `webpack.config.js` file:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
output: {
|
||||
path: require('path').join(__dirname, '/dist/browser/'),
|
||||
chunkFilename: null,
|
||||
filename: 'openstack-lib-browser.js',
|
||||
sourceMapFilename: 'openstack-lib-browser.js.map'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel',
|
||||
exclude: [/node_modules\//],
|
||||
query: {cacheDirectory: true}
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
"node-fetch": "whatwg-fetch"
|
||||
}
|
||||
};
|
||||
|
||||
Node.js Testing
|
||||
---------------
|
||||
|
||||
It was agreed that we're using Jasmine for tests. Jasmine can be easily
|
||||
integrated with Babel by adding the following entry to `jasmine.json`:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"helpers": [
|
||||
"../node_modules/babel-register/lib/node.js"
|
||||
]
|
||||
}
|
||||
|
||||
Browser Testing
|
||||
---------------
|
||||
|
||||
One of the popular test runners Karma can be easily integrated with Babel and
|
||||
Webpack using `karma-webpack` plugin. There is also `karma-babel-preprocessor`
|
||||
module, but it's not needed since integration with Babel is described in
|
||||
`webpack.config.js` file.
|
||||
|
||||
Here is an example of `karma.conf.js` file:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
browsers: ['Chrome', 'Firefox'],
|
||||
plugins: [
|
||||
'karma-webpack'
|
||||
],
|
||||
preprocessors: {
|
||||
'tests/**/*.js': ['webpack']
|
||||
},
|
||||
webpack: require('./webpack.config')
|
||||
});
|
||||
};
|
||||
|
||||
Gulp Integration
|
||||
----------------
|
||||
|
||||
The latest version of Gulp can be easily integrated with Babel just by renaming
|
||||
`gulpfile.js` to `gulpfile.babel.js`. Gulp will transpile the Gulpfile using
|
||||
Babel configuration from `.babelrc` file.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Primary assignee:
|
||||
vkramskikh
|
||||
|
||||
Gerrit Topic
|
||||
------------
|
||||
|
||||
Use Gerrit topic "jsdk_es2015" for all patches related to this spec.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git-review -t jsdk_es2015
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
* Babel and related dependencies should be added.
|
||||
* Babel configuration should be added.
|
||||
* Jasmine and Karma configuration files should be updated to support Babel.
|
||||
* Webpack should be added and configured to use Babel.
|
||||
* NPM scripts to run node/browser builds/tests should be added to package.json.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
We will need project setup documentation for browsers and node applications.
|
||||
All documentation code samples should include examples using all available
|
||||
languages.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
* All code samples should be tested.
|
||||
* Our test suite should be run separately on each transpiled artifact.
|
|
@ -1,107 +0,0 @@
|
|||
::
|
||||
|
||||
Copyright 2016 Mirantis, Inc.
|
||||
|
||||
This work is licensed under a Creative Commons Attribution 3.0
|
||||
Unported License.
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
..
|
||||
|
||||
=======================================
|
||||
Interaction with OpenStack Services API
|
||||
=======================================
|
||||
|
||||
We need to agree on the approach that will be used for interaction with
|
||||
OpenStack services API that would fit needs of library users.
|
||||
|
||||
Problem Description
|
||||
===================
|
||||
|
||||
Choice of a method of communication with OpenStack API it the key decision
|
||||
for the project. The main requirement here is that the chosen method must be
|
||||
isomorphic - the same code must work both with the latest versions of popular
|
||||
browsers (Chrome 50, Firefox 47) and Node v4.
|
||||
|
||||
Proposed Change
|
||||
===============
|
||||
|
||||
This project will use window.fetch() function to interact with OpenStack API.
|
||||
fetch() is a modern XMLHTTPRequest with a frendlier interface, which is
|
||||
supported by majority of the browsers. For browsers which don't support it,
|
||||
there is a very popular `polyfill`_ from Github.
|
||||
|
||||
For node.js `node-fetch`_ module should be used. It implements the same fetch()
|
||||
interface which works on node.js environments.
|
||||
|
||||
fetch() should be called directly from clients, without any extra wrappers.
|
||||
Such wrapper could be added later, for example, to handle caching and parallel
|
||||
requests, but there should be a separate spec for this.
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
There are a few isomorphic libraries for making HTTP requests and interacting
|
||||
with REST API. I think the project should stick to fetch() because:
|
||||
|
||||
* Libraries for making HTTP requests (like `superagent`_) were mostly created
|
||||
when there was no fetch(). They mostly do the same thing - provide a
|
||||
friendlier interface over XMLHTTPRequest. Since there is fetch() standard,
|
||||
it makes very little sense to use them.
|
||||
|
||||
* There is `isomorphic-fetch`_ module, which is more popular than
|
||||
`node-fetch`_. It implements XMLHttpRequest to node.js environment to run
|
||||
browser-specific polyfill. Approach of `node-fetch`_ which implements fetch()
|
||||
using node "http" module (without implementing XMLHTTPRequest) is
|
||||
considered simpler.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Primary assignee:
|
||||
vkramskikh
|
||||
|
||||
Gerrit Topic
|
||||
------------
|
||||
|
||||
Use Gerrit topic "neverland" for all patches related to this spec.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git-review -t neverland
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
* Add node-fetch to the project dependencies.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The process of creating a new OpenStack service client using the approach
|
||||
described in this spec should be documented.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
None
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Tests will be written for node and browsers.
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
Since node-fetch is a project dependency, but it shouldn't be included in the
|
||||
browser build, most likely we need to implement transpiling and build systems
|
||||
first.
|
||||
|
||||
.. _polyfill: https://github.com/github/fetch
|
||||
.. _node-fetch: https://github.com/bitinn/node-fetch
|
||||
.. _superagent: https://github.com/visionmedia/superagent
|
||||
.. _isomorphic-fetch: https://github.com/matthew-andrews/isomorphic-fetch
|
|
@ -1,107 +0,0 @@
|
|||
::
|
||||
|
||||
Copyright <YEARS> <HOLDER> <--UPDATE THESE
|
||||
|
||||
This work is licensed under a Creative Commons Attribution 3.0
|
||||
Unported License.
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
..
|
||||
This template should be in ReSTructured text. Please do not delete
|
||||
any of the sections in this template. If you have nothing to say
|
||||
for a whole section, just write: "None". For help with syntax, see
|
||||
http://sphinx-doc.org/rest.html To test out your formatting, see
|
||||
http://www.tele3.cz/jbar/rest/rest.html
|
||||
|
||||
===============================
|
||||
The Title of Your Specification
|
||||
===============================
|
||||
|
||||
Include the URL of your StoryBoard story:
|
||||
|
||||
https://storyboard.openstack.org/...
|
||||
|
||||
Introduction paragraph -- why are we doing anything?
|
||||
|
||||
Problem Description
|
||||
===================
|
||||
|
||||
A detailed description of the problem.
|
||||
|
||||
Proposed Change
|
||||
===============
|
||||
|
||||
Here is where you cover the change you propose to make in detail. How do you
|
||||
propose to solve this problem?
|
||||
|
||||
If this is one part of a larger effort make it clear where this piece ends. In
|
||||
other words, what's the scope of this effort?
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
This is an optional section, where it does apply we'd just like a demonstration
|
||||
that some thought has been put into why the proposed approach is the best one.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Who is leading the writing of the code? Or is this a blueprint where you're
|
||||
throwing it out there to see who picks it up?
|
||||
|
||||
If more than one person is working on the implementation, please designate the
|
||||
primary author and contact.
|
||||
|
||||
Primary assignee:
|
||||
<launchpad-id or None>
|
||||
|
||||
Can optionally list additional ids if they intend on doing substantial
|
||||
implementation work on this blueprint.
|
||||
|
||||
Gerrit Topic
|
||||
------------
|
||||
|
||||
Use Gerrit topic "<topic_name>" for all patches related to this spec.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git-review -t <topic_name>
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
Work items or tasks -- break the feature up into the things that need to be
|
||||
done to implement it. Those parts might end up being done by different people,
|
||||
but we're mostly trying to understand the timeline for implementation.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Will this require a documentation change? If so, which documents?
|
||||
Will it impact developer workflow? Will additional communication need
|
||||
to be made?
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
Does this introduce any additional security risks, or are there
|
||||
security-related considerations which should be discussed?
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
What tests will be available or need to be constructed in order to
|
||||
validate this? Unit/functional tests, development
|
||||
environments/servers, etc.
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
- Include specific references to specs and/or stories in storyboard, or in
|
||||
other projects, that this one either depends on or is related to.
|
||||
|
||||
- Does this feature require any new library or program dependencies
|
||||
not already in use?
|
|
@ -1,77 +0,0 @@
|
|||
import webpackConfig from './webpack.config.babel'
|
||||
import path from 'path'
|
||||
|
||||
export default (config) => {
|
||||
// test mode based on basePath parameter (eg. test/unit, test/functional)
|
||||
const testDir = config.basePath ? path.basename(config.basePath) : 'unit'
|
||||
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: 'test/' + testDir,
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'**/*.js'
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'helpers/**/*.js'
|
||||
],
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'**/*.js': ['webpack']
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress', 'coverage'],
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: false,
|
||||
|
||||
// level of logging
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
// Execute tests once, then exit.
|
||||
singleRun: true,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['Firefox', 'Chrome'],
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
concurrency: Infinity,
|
||||
|
||||
// We simulate a fully compiled browser application by using webpack. Since we only really
|
||||
// use this during testing, the configuration is kept here.
|
||||
webpack: webpackConfig,
|
||||
|
||||
webpackMiddleware: {
|
||||
// Don't spam the console.
|
||||
noInfo: true
|
||||
},
|
||||
|
||||
// Generate a coverage report in /cover/karma
|
||||
coverageReporter: {
|
||||
type: 'html', // produces a html document after code is run
|
||||
dir: '../../cover/' + testDir + '/browser/' // path to created html doc
|
||||
}
|
||||
|
||||
})
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
require('@babel/register')
|
||||
module.exports = require('./karma.conf.babel').default
|
71
package.json
71
package.json
|
@ -1,71 +0,0 @@
|
|||
{
|
||||
"name": "openstack-lib",
|
||||
"version": "0.0.2",
|
||||
"description": "JavaScript API library for OpenStack.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"configure-devstack": "babel-node ./configure-devstack.js",
|
||||
"test": "npm run test:node",
|
||||
"test:node": "nyc babel-node test/unit/run.js",
|
||||
"test:browser": "karma start",
|
||||
"posttest:node": "nyc check-coverage",
|
||||
"functional-test": "npm run functional-test:node ; npm run functional-test:browser",
|
||||
"functional-test:node": "nyc --dir cover/functional/node babel-node test/functional/run.js",
|
||||
"functional-test:browser": "karma start --basePath test/functional/",
|
||||
"lint": "eslint ./",
|
||||
"build": "babel src -d dist && webpack",
|
||||
"docs": "npm run jsdoc; (cd ./doc && make html)",
|
||||
"jsdoc": "jsdoc -t node_modules/jsdoc-sphinx/template -d ./doc/source/jsdoc ./src"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.openstack.org/openstack/js-openstack-lib"
|
||||
},
|
||||
"keywords": [
|
||||
"openstack"
|
||||
],
|
||||
"author": "OpenStack <openstack-dev@lists.openstack.org>",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "http://www.openstack.org/",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"loglevel": "^1.4.1",
|
||||
"url-parse": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/node": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-plugin-transform-inline-environment-variables": "^6.8.0",
|
||||
"eslint": "^7.1.0",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"fetch-mock": "^5.0.5",
|
||||
"jasmine": "^3.5.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"jsdoc": "^3.4.0",
|
||||
"jsdoc-sphinx": "0.0.6",
|
||||
"json-loader": "^0.5.7",
|
||||
"karma": "^5.0.9",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage": "^2.0.2",
|
||||
"karma-firefox-launcher": "^1.3.0",
|
||||
"karma-jasmine": "^3.1.1",
|
||||
"karma-webpack": "^4.0.2",
|
||||
"nyc": "^15.0.1",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"yaml-loader": "^0.6.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
- fetch-javascript-output
|
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
# NOTE(yoctozepto): devstack starts WSGI-based, API services too early
|
||||
# to make post-config apply to them
|
||||
# see: https://bugs.launchpad.net/devstack/+bug/1860287
|
||||
- name: "Restart devstack API services"
|
||||
command: "systemctl restart devstack@{{ item }}"
|
||||
become: True
|
||||
loop:
|
||||
- keystone
|
||||
- g-api
|
||||
- q-svc
|
||||
- n-api
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
- ensure-javascript-build-tool
|
||||
- nodejs-test-dependencies
|
||||
tasks:
|
||||
# NOTE(yoctozepto): nodejs-test-dependencies role installs only
|
||||
# chromium-browser but we need firefox too
|
||||
- name: Install firefox
|
||||
apt:
|
||||
name: firefox
|
||||
become: true
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
- revoke-sudo
|
||||
- js-package-manager
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import AbstractService from './util/abstractService'
|
||||
|
||||
/**
|
||||
* A list of all supported versions. Please keep this array sorted by most recent.
|
||||
*
|
||||
* @type {Array}
|
||||
* @ignore
|
||||
*/
|
||||
const supportedGlanceVersions = [
|
||||
'v2.3'
|
||||
]
|
||||
|
||||
export default class Glance extends AbstractService {
|
||||
/**
|
||||
* This class provides direct, idempotent, low-level access to the Glance API of a specific
|
||||
* cloud. The constructor requires that you provide a specific glance interface endpoint
|
||||
* descriptor, as received from keystone's catalog list.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* region_id: "RegionOne",
|
||||
* url: "http://127.0.0.1:9292",
|
||||
* region: "RegionOne",
|
||||
* interface: "admin",
|
||||
* id: "0b8b5f0f14904136ab5a4f83f27ec49a"
|
||||
* }
|
||||
* @param {{}} endpointConfig The configuration element for a specific glance endpoint.
|
||||
*/
|
||||
constructor (endpointConfig) {
|
||||
// Sanity checks.
|
||||
if (!endpointConfig || !endpointConfig.url) {
|
||||
throw new Error('An endpoint configuration is required.')
|
||||
}
|
||||
// Clone the config, so that this instance is immutable
|
||||
// at runtime (no modifying the config after the fact).
|
||||
endpointConfig = Object.assign({}, endpointConfig)
|
||||
|
||||
super(endpointConfig.url, supportedGlanceVersions)
|
||||
this._config = endpointConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* List the images available on glance.
|
||||
*
|
||||
* @param {(String|Promise.<T>)} token An authorization token, or a promise which will resolve
|
||||
* into one.
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of images.
|
||||
*/
|
||||
imageList (token = null) {
|
||||
return this
|
||||
._requestComponents(token)
|
||||
.then(([url, headers]) => this.http.httpRequest('GET', `${url}images`, headers))
|
||||
.then((response) => response.json())
|
||||
.then((body) => body.images)
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export { default as Keystone } from './keystone'
|
||||
export { default as Glance } from './glance'
|
||||
export { default as Neutron } from './neutron'
|
||||
export { default as OpenStack } from './openstack'
|
203
src/keystone.js
203
src/keystone.js
|
@ -1,203 +0,0 @@
|
|||
import AbstractService from './util/abstractService'
|
||||
|
||||
/**
|
||||
* A list of all supported versions. Please keep this array sorted by most recent.
|
||||
*
|
||||
* @type {Array}
|
||||
* @ignore
|
||||
*/
|
||||
const supportedKeystoneVersions = [
|
||||
'v3.1'
|
||||
]
|
||||
|
||||
export default class Keystone extends AbstractService {
|
||||
/**
|
||||
* This class provides direct, idempotent, low-level access to the Keystone API of a specific
|
||||
* cloud. The constructor requires that you provide a configuration object for a specific
|
||||
* cloud, formatted as per the os-client-config specification of clouds.yaml. An important
|
||||
* difference is that it does not accept the entire clouds.yaml structure, only the subsection
|
||||
* that refers to a specific cloud.
|
||||
*
|
||||
* @param {{}} cloudConfig The configuration object for a specific cloud.
|
||||
* @see http://docs.openstack.org/developer/os-client-config/#site-specific-file-locations
|
||||
*/
|
||||
constructor (cloudConfig) {
|
||||
// Sanity checks.
|
||||
if (!cloudConfig) {
|
||||
throw new Error('A configuration is required.')
|
||||
}
|
||||
// Clone the config, so that this instance is immutable
|
||||
// at runtime (no modifying the config after the fact).
|
||||
cloudConfig = Object.assign({}, cloudConfig)
|
||||
|
||||
super(cloudConfig.auth.auth_url, supportedKeystoneVersions)
|
||||
this.cloudConfig = cloudConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* This method provides a safe method for reading values deep inside of an object structure,
|
||||
* without encountering TypeErrors.
|
||||
*
|
||||
* @param {string} path A string representing the dot notation of a config path to read.
|
||||
* @private
|
||||
* @returns {String} The value found in the config, or null.
|
||||
* @ignore
|
||||
*/
|
||||
_safeConfigGet (path) {
|
||||
const segments = path.split('.')
|
||||
let pointer = this.cloudConfig
|
||||
while (segments.length > 0) {
|
||||
const prop = segments.shift()
|
||||
if (Object.prototype.hasOwnProperty.call(pointer, prop)) {
|
||||
pointer = pointer[prop]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return pointer
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the raw API versions available.
|
||||
*
|
||||
* @returns {Promise.<Object[]>} A promise that will resolve with the list of raw versions.
|
||||
* @protected
|
||||
*/
|
||||
_rawVersions () {
|
||||
return super._rawVersions()
|
||||
.then((versions) => versions.values)
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a token from the provided credentials. Credentials will be read from the
|
||||
* configuration, unless they have been explicitly provided.
|
||||
*
|
||||
* NOTE: This method is only applicable if the password auth plugin on keystone is enabled.
|
||||
* Other auth methods will have to be provided by third-party developers.
|
||||
*
|
||||
* @param {Object} credentials Optional credentials.
|
||||
* @param {String} credentials.user_id An optional user ID.
|
||||
* @param {String} credentials.username An optional user name.
|
||||
* @param {String} credentials.password An optional password.
|
||||
* @param {String} credentials.user_domain_id An optional user domain ID.
|
||||
* Not required if a user ID is given.
|
||||
* @param {String} credentials.user_domain_name An optional user domain name.
|
||||
* Not required if a user ID is given.
|
||||
* @param {String} credentials.project_id An optional project ID.
|
||||
* @param {String} credentials.project_name An optional project name.
|
||||
* @param {String} credentials.project_domain_id An optional project domain ID.
|
||||
* Not required if a project ID is given.
|
||||
* @param {String} credentials.project_domain_name An optional project domain name.
|
||||
* Not required if a project ID is given.
|
||||
* @returns {Promise.<T>} A promise which will resolve with a valid token.
|
||||
*/
|
||||
tokenIssue ({
|
||||
user_id: userId = this._safeConfigGet('auth.user_id'),
|
||||
username = this._safeConfigGet('auth.username'),
|
||||
password = this._safeConfigGet('auth.password'),
|
||||
user_domain_id: userDomainId = this._safeConfigGet('auth.user_domain_id'),
|
||||
user_domain_name: userDomainName = this._safeConfigGet('auth.user_domain_name'),
|
||||
project_id: projectId = this._safeConfigGet('auth.project_id'),
|
||||
project_name: projectName = this._safeConfigGet('auth.project_name'),
|
||||
project_domain_id: projectDomainId = this._safeConfigGet('auth.project_domain_id'),
|
||||
project_domain_name: projectDomainName = this._safeConfigGet('auth.project_domain_name')
|
||||
} = {}) {
|
||||
let project
|
||||
const user = { password }
|
||||
|
||||
if (userId) {
|
||||
user.id = userId
|
||||
} else if (username) {
|
||||
user.name = username
|
||||
if (userDomainId) {
|
||||
user.domain = { id: userDomainId }
|
||||
} else if (userDomainName) {
|
||||
user.domain = { name: userDomainName }
|
||||
} else {
|
||||
user.domain = { id: 'default' }
|
||||
}
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
project = { id: projectId }
|
||||
} else if (projectName) {
|
||||
project = { name: projectName }
|
||||
if (projectDomainId) {
|
||||
project.domain = { id: projectDomainId }
|
||||
} else if (projectDomainName) {
|
||||
project.domain = { name: projectDomainName }
|
||||
} else {
|
||||
project.domain = { id: 'default' }
|
||||
}
|
||||
}
|
||||
|
||||
const body = {
|
||||
auth: {
|
||||
identity: {
|
||||
methods: ['password'],
|
||||
password: { user }
|
||||
},
|
||||
scope: project ? { project } : 'unscoped'
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
.serviceEndpoint()
|
||||
.then((url) => this.http.httpPost(`${url}auth/tokens`, body))
|
||||
.then((response) => {
|
||||
return response.headers.get('X-Subject-Token')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an authorization token.
|
||||
*
|
||||
* @param {String} token The token to revoke.
|
||||
* @param {String} adminToken An optional admin token.
|
||||
* @returns {Promise.<T>} A promise which will resolve if the token has been successfully revoked.
|
||||
*/
|
||||
tokenRevoke (token, adminToken = null) {
|
||||
return Promise
|
||||
.all([this.serviceEndpoint(), token, adminToken])
|
||||
.then(([url, token, adminToken]) => {
|
||||
return [url, {
|
||||
'X-Subject-Token': token,
|
||||
'X-Auth-Token': adminToken || token
|
||||
}]
|
||||
})
|
||||
.then(([url, headers]) => this.http.httpRequest('DELETE', `${url}auth/tokens`, headers))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a token.
|
||||
*
|
||||
* @param {String} token The authorization token.
|
||||
* @returns {Promise.<T>} A promise which will resolve with information about the token.
|
||||
*/
|
||||
tokenInfo (token) {
|
||||
return Promise
|
||||
.all([this.serviceEndpoint(), token])
|
||||
.then(([url, token]) => {
|
||||
return [url, {
|
||||
'X-Subject-Token': token,
|
||||
'X-Auth-Token': token
|
||||
}]
|
||||
})
|
||||
.then(([url, headers]) => this.http.httpRequest('GET', `${url}auth/tokens`, headers))
|
||||
.then((response) => response.json())
|
||||
}
|
||||
|
||||
/**
|
||||
* List the service catalog for the configured cloud.
|
||||
*
|
||||
* @param {String} token The authorization token.
|
||||
* @returns {Promise.<T>} A promise which will resolve with the service catalog.
|
||||
*/
|
||||
catalogList (token = null) {
|
||||
return this
|
||||
._requestComponents(token)
|
||||
.then(([url, headers]) => this.http.httpRequest('GET', `${url}auth/catalog`, headers))
|
||||
.then((response) => response.json())
|
||||
.then((body) => body.catalog)
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Internap.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import AbstractService from './util/abstractService'
|
||||
|
||||
/**
|
||||
* A list of all supported versions. Please keep this array sorted by most recent.
|
||||
*
|
||||
* @type {Array}
|
||||
* @ignore
|
||||
*/
|
||||
const supportedNeutronVersions = [
|
||||
'v2.0'
|
||||
]
|
||||
|
||||
export default class Neutron extends AbstractService {
|
||||
/**
|
||||
* This class provides direct, idempotent, low-level access to the Neutron API of a specific
|
||||
* cloud. The constructor requires that you provide a specific neutron interface endpoint
|
||||
* descriptor, as received from keystone's catalog list.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* region_id: "RegionOne",
|
||||
* url: "http://127.0.0.1:9696",
|
||||
* region: "RegionOne",
|
||||
* interface: "admin",
|
||||
* id: "4f08823e667345478b6e40fab8373c0f"
|
||||
* }
|
||||
* @param {{}} endpointConfig The configuration element for a specific glance endpoint.
|
||||
*/
|
||||
constructor (endpointConfig) {
|
||||
// Sanity checks.
|
||||
if (!endpointConfig || !endpointConfig.url) {
|
||||
throw new Error('An endpoint configuration is required.')
|
||||
}
|
||||
// Clone the config, so that this instance is immutable
|
||||
// at runtime (no modifying the config after the fact).
|
||||
endpointConfig = Object.assign({}, endpointConfig)
|
||||
|
||||
super(endpointConfig.url, supportedNeutronVersions)
|
||||
}
|
||||
|
||||
/**
|
||||
* List the networks available on neutron.
|
||||
*
|
||||
* @param {String} token An authorization token, or a promise which will resolve into one.
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of networks.
|
||||
*/
|
||||
networkList (token = null) {
|
||||
return this
|
||||
._requestComponents(token)
|
||||
.then(([url, headers]) => this.http.httpRequest('GET', `${url}/networks`, headers))
|
||||
.then((response) => response.json())
|
||||
.then((body) => body.networks)
|
||||
}
|
||||
}
|
71
src/nova.js
71
src/nova.js
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Michael Krotscheck.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import AbstractService from './util/abstractService'
|
||||
|
||||
/**
|
||||
* A list of all supported versions. Please keep this array sorted by most recent.
|
||||
*
|
||||
* @type {Array}
|
||||
* @ignore
|
||||
*/
|
||||
const supportedNovaVersions = [
|
||||
'v2.1'
|
||||
]
|
||||
|
||||
export default class Nova extends AbstractService {
|
||||
/**
|
||||
* This class provides direct, idempotent, low-level access to the Nova API of a specific
|
||||
* cloud. The constructor requires that you provide a specific nova interface endpoint
|
||||
* descriptor, as received from keystone's catalog list.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* region_id: "RegionOne",
|
||||
* url: "http://127.0.0.1:8774/",
|
||||
* region: "RegionOne",
|
||||
* interface: "admin",
|
||||
* id: "0b8b5f0f14904136ab5a4f83f27ec49a"
|
||||
* }
|
||||
* @param {{}} endpointConfig The configuration element for a specific nova endpoint.
|
||||
*/
|
||||
constructor (endpointConfig) {
|
||||
// Sanity checks.
|
||||
if (!endpointConfig || !endpointConfig.url) {
|
||||
throw new Error('An endpoint configuration is required.')
|
||||
}
|
||||
// Clone the config, so that this instance is immutable
|
||||
// at runtime (no modifying the config after the fact).
|
||||
endpointConfig = Object.assign({}, endpointConfig)
|
||||
|
||||
super(endpointConfig.url, supportedNovaVersions)
|
||||
this._config = endpointConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* List the flavors available on nova.
|
||||
*
|
||||
* @param {String} token An authorization token, or a promise which will resolve into one.
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of flavors.
|
||||
*/
|
||||
flavorList (token = null) {
|
||||
return this
|
||||
._requestComponents(token)
|
||||
.then(([url, headers]) => this.http.httpRequest('GET', `${url}flavors`, headers))
|
||||
.then((response) => response.json())
|
||||
.then((body) => body.flavors)
|
||||
}
|
||||
}
|
144
src/openstack.js
144
src/openstack.js
|
@ -1,144 +0,0 @@
|
|||
import Keystone from './keystone'
|
||||
import Neutron from './neutron'
|
||||
import Glance from './glance'
|
||||
import Nova from './nova'
|
||||
|
||||
export default class OpenStack {
|
||||
/**
|
||||
* Create wrapper class that takes clouds.yaml instance
|
||||
*
|
||||
* @param {{}} cloudConfig The configuration object for a specific cloud.
|
||||
*/
|
||||
constructor (cloudConfig) {
|
||||
// Sanity checks.
|
||||
if (!cloudConfig) {
|
||||
throw new Error('A configuration is required.')
|
||||
}
|
||||
// Clone the config, so that this instance is immutable
|
||||
// at runtime (no modifying the config after the fact).
|
||||
cloudConfig = Object.assign({}, cloudConfig)
|
||||
|
||||
this.cloudConfig = cloudConfig
|
||||
}
|
||||
|
||||
getConfig () {
|
||||
// Returns the config instance
|
||||
return this.cloudConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* List the networks available.
|
||||
*
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of networks.
|
||||
*/
|
||||
networkList () {
|
||||
return this._neutron
|
||||
.then((neutron) => neutron.networkList(this._token))
|
||||
}
|
||||
|
||||
/**
|
||||
* List the images available on glance.
|
||||
*
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of images.
|
||||
*/
|
||||
imageList () {
|
||||
return this._glance
|
||||
.then((glance) => glance.imageList(this._token))
|
||||
}
|
||||
|
||||
/**
|
||||
* List the flavors available on nova.
|
||||
*
|
||||
* @returns {Promise.<T>} A promise which will resolve with the list of flavors.
|
||||
*/
|
||||
flavorList () {
|
||||
return this._nova
|
||||
.then((nova) => nova.flavorList(this._token))
|
||||
}
|
||||
|
||||
/**
|
||||
* Keystone component.
|
||||
*
|
||||
* @returns {Promise.<Keystone>} 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.<Neutron>} 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Glance component.
|
||||
*
|
||||
* @returns {Promise.<Glance>} A promise which will resolve with Glance instance.
|
||||
* @private
|
||||
*/
|
||||
get _glance () {
|
||||
if (!this._glancePromise) {
|
||||
this._glancePromise = this._getComponentConfigFor('glance')
|
||||
.then((componentConfig) => new Glance(componentConfig))
|
||||
}
|
||||
return this._glancePromise
|
||||
}
|
||||
|
||||
/**
|
||||
* Nova component.
|
||||
*
|
||||
* @returns {Promise.<Nova>} A promise which will resolve with Nova instance.
|
||||
* @private
|
||||
*/
|
||||
get _nova () {
|
||||
if (!this._novaPromise) {
|
||||
this._novaPromise = this._getComponentConfigFor('nova')
|
||||
.then((componentConfig) => new Nova(componentConfig))
|
||||
}
|
||||
return this._novaPromise
|
||||
}
|
||||
|
||||
/**
|
||||
* Token issued from Keystone.
|
||||
*
|
||||
* @returns {Promise.<T>} 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) {
|
||||
const config = this.getConfig()
|
||||
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) => {
|
||||
return endpoint.region === config.region_name && endpoint.interface === 'public'
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Http from './http'
|
||||
import Version from './version'
|
||||
import URL from 'url-parse'
|
||||
|
||||
export default class AbstractService {
|
||||
/**
|
||||
* This class provides an abstract implementation of our services, which includes logic common to
|
||||
* all of our services.
|
||||
*
|
||||
* @param {string} endpointUrl The endpoint URL.
|
||||
* @param {Array} supportedVersions The list of all supported versions.
|
||||
*/
|
||||
constructor (endpointUrl, supportedVersions) {
|
||||
this._endpointUrl = endpointUrl
|
||||
this._supportedVersions = supportedVersions
|
||||
}
|
||||
|
||||
/**
|
||||
* Our HTTP service instance.
|
||||
*
|
||||
* @returns {Http} Our HTTP service instance.
|
||||
*/
|
||||
get http () {
|
||||
if (!this._http) {
|
||||
this._http = new Http()
|
||||
}
|
||||
return this._http
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all supported versions.
|
||||
*
|
||||
* @returns {Array} The list of all supported versions, or empty array.
|
||||
*/
|
||||
get supportedVersions () {
|
||||
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.
|
||||
*
|
||||
* @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) => {
|
||||
this.http
|
||||
.httpGet(this._endpointUrl)
|
||||
.catch((response) => {
|
||||
if (response.status === 401) {
|
||||
const rootUrl = new URL(this._endpointUrl)
|
||||
rootUrl.set('pathname', '/')
|
||||
rootUrl.set('query', '')
|
||||
rootUrl.set('hash', '')
|
||||
|
||||
return this.http.httpGet(rootUrl.href)
|
||||
} else {
|
||||
throw response
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((body) => resolve(body.versions))
|
||||
.catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the API version declaration that is currently in use by this instance.
|
||||
*
|
||||
* @returns {Promise.<Version>} A promise that will resolve with the specific API version.
|
||||
*/
|
||||
version () {
|
||||
return this
|
||||
.versions()
|
||||
.then((versions) => {
|
||||
for (const version of versions) {
|
||||
if (this.supportedVersions.find(version.supports)) {
|
||||
return version
|
||||
}
|
||||
}
|
||||
throw new Error('No supported API version available.')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root API endpoint for the current supported glance version.
|
||||
*
|
||||
* @returns {Promise.<T>|*} A promise which will resolve with the endpoint URL string.
|
||||
*/
|
||||
serviceEndpoint () {
|
||||
if (!this._endpointPromise) {
|
||||
this._endpointPromise = this.version()
|
||||
.then((version) => {
|
||||
if (version.links) {
|
||||
for (let i = 0; i < version.links.length; i++) {
|
||||
const link = version.links[i]
|
||||
if (link.rel === 'self' && link.href) {
|
||||
return link.href
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error('No service endpoint discovered.')
|
||||
})
|
||||
}
|
||||
return this._endpointPromise
|
||||
}
|
||||
|
||||
/**
|
||||
* This method builds common components for a request to the implemented service.
|
||||
* It converts any passed token into a promise, resolves the base URL, and then passes
|
||||
* the results as an .all() promise, which may be destructured in a followup request.
|
||||
*
|
||||
* @param {Promise|String} token A promise, or string, representing a token.
|
||||
* @returns {Promise} A promise which resolves with [url, token].
|
||||
* @private
|
||||
*/
|
||||
_requestComponents (token = null) {
|
||||
// Make sure the token is a promise.
|
||||
const headerPromise = Promise
|
||||
.resolve(token)
|
||||
.then((token) => {
|
||||
if (token) {
|
||||
return {
|
||||
'X-Auth-Token': token
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
return Promise.all([this.serviceEndpoint(), headerPromise])
|
||||
}
|
||||
}
|
142
src/util/http.js
142
src/util/http.js
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import 'isomorphic-fetch'
|
||||
import log from 'loglevel'
|
||||
|
||||
/**
|
||||
* This utility class provides an abstraction layer for HTTP calls via fetch(). Its purpose is
|
||||
* to provide common, SDK-wide behavior for all HTTP requests. Included are:
|
||||
*
|
||||
* - Access to default headers.
|
||||
* - Convenience GET/PUT/POST/DELETE methods.
|
||||
* - Passing 4xx and 5xx responses to the catch() handler.
|
||||
*
|
||||
* In the future, this class chould also be extended to provide:
|
||||
*
|
||||
* - Some form of progress() support for large uploads and downloads (perhaps via introduction of Q)
|
||||
* - Convenience decoding of the response body, depending on Content-Type.
|
||||
* - Internal error handling (At this time, HTTP errors are passed to then() rather than catch()).
|
||||
* - Other features.
|
||||
*/
|
||||
export default class Http {
|
||||
/**
|
||||
* The default headers which will be sent with every request. A copy of these headers will be
|
||||
* added to the Request instance passed through the interceptor chain, and may be
|
||||
* modified there.
|
||||
*
|
||||
* @returns {{string: string}} A mapping of 'headerName': 'headerValue'
|
||||
*/
|
||||
get defaultHeaders () {
|
||||
return this._defaultHeaders
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HTTP handler.
|
||||
*/
|
||||
constructor () {
|
||||
// Add default response interceptors.
|
||||
this._defaultHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a decorated HTTP request.
|
||||
*
|
||||
* @param {String} method The HTTP method.
|
||||
* @param {String} url The request URL.
|
||||
* @param {{}} headers A map of HTTP headers.
|
||||
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
||||
* @returns {Promise} A promise which will resolve with the processed request response.
|
||||
*/
|
||||
httpRequest (method, url, headers = {}, body) {
|
||||
// Sanitize the headers...
|
||||
headers = Object.assign({}, headers, this.defaultHeaders)
|
||||
|
||||
// Build the request
|
||||
const init = { method, headers }
|
||||
|
||||
// The Request() constructor will throw an error if the method is GET/HEAD, and there's a body.
|
||||
if (['GET', 'HEAD'].indexOf(method) === -1 && body) {
|
||||
init.body = JSON.stringify(body)
|
||||
}
|
||||
const request = new Request(url, init)
|
||||
|
||||
// Build the wrapper promise.
|
||||
return new Promise((resolve, reject) => {
|
||||
log.debug('-->', `HTTP ${method}`, url, JSON.stringify(headers), JSON.stringify(body))
|
||||
const promise = fetch(request.url, init)
|
||||
|
||||
// Fetch will treat all http responses (2xx, 3xx, 4xx, 5xx, etc) as successful responses.
|
||||
// This will catch all 4xx and 5xx responses and return them to the catch() handler. Note
|
||||
// that it's up to the downstream developer to determine whether what they received is an
|
||||
// error or a failed response.
|
||||
promise.then((response) => {
|
||||
log.debug('<--', `HTTP ${response.status}`)
|
||||
if (response.status >= 400) {
|
||||
return reject(response)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
})
|
||||
|
||||
promise.then((response) => resolve(response), (error) => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a raw GET request against a particular URL.
|
||||
*
|
||||
* @param {String} url The request URL.
|
||||
* @returns {Promise} A promise which will resolve with the processed request response.
|
||||
*/
|
||||
httpGet (url) {
|
||||
return this.httpRequest('GET', url, {}, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a raw PUT request against a particular URL.
|
||||
*
|
||||
* @param {String} url The request URL.
|
||||
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
||||
* @returns {Promise} A promise which will resolve with the processed request response.
|
||||
*/
|
||||
httpPut (url, body) {
|
||||
return this.httpRequest('PUT', url, {}, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a raw POST request against a particular URL.
|
||||
*
|
||||
* @param {String} url The request URL.
|
||||
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
||||
* @returns {Promise} A promise which will resolve with the processed request response.
|
||||
*/
|
||||
httpPost (url, body) {
|
||||
return this.httpRequest('POST', url, {}, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a raw DELETE request against a particular URL.
|
||||
*
|
||||
* @param {String} url The request URL.
|
||||
* @returns {Promise} A promise which will resolve with the processed request response.
|
||||
*/
|
||||
httpDelete (url) {
|
||||
return this.httpRequest('DELETE', url, {}, null)
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A simple version parser, able to parse various version strings used in OpenStack into a
|
||||
* comparable instance.
|
||||
*/
|
||||
export default class Version {
|
||||
/**
|
||||
* The name of the service.
|
||||
*
|
||||
* @returns {String|*|null} The name of the service, or null.
|
||||
*/
|
||||
get service () {
|
||||
return this._service || null
|
||||
}
|
||||
|
||||
/**
|
||||
* The major version.
|
||||
*
|
||||
* @returns {Number} The major version number
|
||||
*/
|
||||
get major () {
|
||||
return this._major || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* The minor version.
|
||||
*
|
||||
* @returns {Number} The minor version number
|
||||
*/
|
||||
get minor () {
|
||||
return this._minor || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* The patch version.
|
||||
*
|
||||
* @returns {Number} The patch version number.
|
||||
*/
|
||||
get patch () {
|
||||
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} versionString The version string for this service.
|
||||
*/
|
||||
constructor (service, versionString) {
|
||||
// Sanitize input
|
||||
if (typeof service !== 'string') {
|
||||
service = undefined
|
||||
}
|
||||
if (typeof versionString !== 'string') {
|
||||
versionString = undefined
|
||||
}
|
||||
|
||||
if (versionString === undefined) {
|
||||
versionString = service
|
||||
} else {
|
||||
this._service = service
|
||||
}
|
||||
|
||||
// Sanity check before running regex.
|
||||
if (!versionString || !versionString.match) {
|
||||
return
|
||||
}
|
||||
|
||||
const results = versionString.match(/^(([^ ]+) )?v?(([0-9]+)(\.([0-9]+)(.([0-9]+))?)?)$/)
|
||||
if (results) {
|
||||
this._service = results[2] || this._service // regex takes precedence
|
||||
this._major = parseInt(results[4], 10)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare this instance to another instance or version string.
|
||||
*
|
||||
* @param {String|Version} version The version to compare to.
|
||||
* @returns {boolean} True if they are exactly the same, otherwise false.
|
||||
*/
|
||||
equals (version) {
|
||||
if (!(version instanceof Version)) {
|
||||
// is it a parseable string?
|
||||
if (typeof version === 'string') {
|
||||
version = new Version(version)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return version.major === this.major &&
|
||||
version.minor === this.minor &&
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import config from './helpers/cloudsConfig'
|
||||
import Version from '../../src/util/version'
|
||||
import Glance from '../../src/glance'
|
||||
import Keystone from '../../src/keystone'
|
||||
import log from 'loglevel'
|
||||
|
||||
log.setLevel('DEBUG')
|
||||
|
||||
describe('Glance', () => {
|
||||
// Create a keystone instance and extract the glance API endpoint.
|
||||
const devstackConfig = config.clouds.devstack
|
||||
const keystone = new Keystone(devstackConfig)
|
||||
const tokenPromise = keystone.tokenIssue()
|
||||
|
||||
const configPromise = tokenPromise
|
||||
.then((token) => keystone.catalogList(token))
|
||||
.then((catalog) => catalog.find((entry) => entry.name === 'glance'))
|
||||
.then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public'))
|
||||
|
||||
describe('versions()', () => {
|
||||
it('should return a list of all versions available on this clouds\' glance', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Glance(config))
|
||||
.then((glance) => glance.versions())
|
||||
.then((versions) => {
|
||||
// Quick sanity check.
|
||||
expect(versions.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('version()', () => {
|
||||
/**
|
||||
* This test acts as a canary, to inform the SDK developers that the Glance API
|
||||
* has changed in a significant way.
|
||||
*/
|
||||
it('should return a supported version.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Glance(config))
|
||||
.then((glance) => glance.version())
|
||||
.then((apiVersion) => {
|
||||
expect(apiVersion instanceof Version).not.toBeFalsy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('imageList()', () => {
|
||||
/**
|
||||
* Assert that we can get a list of images.
|
||||
*/
|
||||
it('should return a supported version.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Glance(config))
|
||||
.then((glance) => glance.imageList(tokenPromise))
|
||||
.then((images) => {
|
||||
expect(images.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,8 +0,0 @@
|
|||
/* eslint no-sync: "off" */
|
||||
import yaml from 'js-yaml'
|
||||
import fs from 'fs'
|
||||
import cloudsYamlPath from './cloudsYamlPath'
|
||||
|
||||
const clouds = yaml.safeLoad(fs.readFileSync(cloudsYamlPath, 'utf8'))
|
||||
|
||||
export default clouds
|
|
@ -1,22 +0,0 @@
|
|||
/* eslint no-process-env: "off", no-sync: "off" */
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const resolvePaths = [
|
||||
'./clouds.yaml',
|
||||
process.env.HOME + '/.config/openstack/clouds.yaml',
|
||||
'/etc/openstack/clouds.yaml'
|
||||
]
|
||||
|
||||
function fileExists (path) {
|
||||
try {
|
||||
fs.statSync(path)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const cloudFiles = resolvePaths.filter(fileExists)
|
||||
|
||||
export default cloudFiles.length > 0 ? path.resolve(cloudFiles[0]) : 'clouds.yaml'
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"spec_dir": "test/functional",
|
||||
"spec_files": [
|
||||
"**/*[tT]est.js"
|
||||
],
|
||||
"helpers": [
|
||||
"../../node_modules/babel-register/lib/node.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Version from '../../src/util/version'
|
||||
import Keystone from '../../src/keystone'
|
||||
import config from './helpers/cloudsConfig'
|
||||
import log from 'loglevel'
|
||||
|
||||
log.setLevel('DEBUG')
|
||||
|
||||
describe('Keystone', () => {
|
||||
const devstackConfig = config.clouds.devstack
|
||||
const adminConfig = config.clouds['devstack-admin']
|
||||
const keystone = new Keystone(devstackConfig)
|
||||
|
||||
describe('versions()', () => {
|
||||
it('should return a list of all versions available on this clouds\' keystone', (done) => {
|
||||
keystone.versions()
|
||||
.then((versions) => {
|
||||
// Quick sanity check.
|
||||
expect(versions.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('version()', () => {
|
||||
/**
|
||||
* This test acts as a canary, to inform the SDK developers that the Keystone API
|
||||
* has changed in a significant way.
|
||||
*/
|
||||
it('should return a supported version.', (done) => {
|
||||
keystone.version()
|
||||
.then((apiVersion) => {
|
||||
expect(apiVersion instanceof Version).not.toBeFalsy()
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenIssue()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
keystone = new Keystone(config.clouds.devstack)
|
||||
})
|
||||
|
||||
it('should "just work" by using provided credentials from the config.', (done) => {
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
expect(token).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should permit passing your own user, password, and project.', (done) => {
|
||||
keystone
|
||||
.tokenIssue(adminConfig.auth)
|
||||
.then((token) => {
|
||||
expect(token).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an exception if invalid username and password are provided.', (done) => {
|
||||
keystone
|
||||
.tokenIssue({
|
||||
username: 'foo',
|
||||
password: 'bar'
|
||||
})
|
||||
.then((token) => done.fail(token))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an exception if invalid project is provided.', (done) => {
|
||||
keystone
|
||||
.tokenIssue({
|
||||
project_id: 'foo',
|
||||
project_name: 'bar'
|
||||
})
|
||||
.then((token) => done.fail(token))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an exception if invalid user domain is provided.', (done) => {
|
||||
keystone
|
||||
.tokenIssue({
|
||||
user_domain_id: 'foo',
|
||||
user_domain_name: 'bar'
|
||||
})
|
||||
.then((token) => done.fail(token))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenRevoke()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
keystone = new Keystone(config.clouds.devstack)
|
||||
})
|
||||
|
||||
it('should permit self-revocation.', (done) => {
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
return keystone.tokenRevoke(token)
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.status).toBe(204) // No content
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should allow an admin to revoke another token.', (done) => {
|
||||
let adminToken
|
||||
const adminKeystone = new Keystone(adminConfig)
|
||||
|
||||
adminKeystone.tokenIssue() // Get an admin token.
|
||||
.then((token) => {
|
||||
adminToken = token
|
||||
return keystone.tokenIssue() // Regular token.
|
||||
})
|
||||
.then((token) => keystone.tokenRevoke(token, adminToken))
|
||||
.then((response) => {
|
||||
expect(response.status).toBe(204) // No content
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an exception if invalid token is provided.', (done) => {
|
||||
keystone
|
||||
.tokenRevoke('not_a_valid_token')
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenInfo()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
keystone = new Keystone(config.clouds.devstack)
|
||||
})
|
||||
|
||||
it('should retrieve info about a token.', (done) => {
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
return keystone.tokenInfo(token)
|
||||
})
|
||||
.then((info) => {
|
||||
expect('token' in info).toBe(true)
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an exception if invalid token is provided.', (done) => {
|
||||
keystone
|
||||
.tokenRevoke('not_a_valid_token')
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('catalogList()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
keystone = new Keystone(config.clouds.devstack)
|
||||
})
|
||||
|
||||
it('should list a catalog.', (done) => {
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
return keystone.catalogList(token)
|
||||
})
|
||||
.then((catalog) => {
|
||||
expect(catalog.length).not.toBe(0)
|
||||
done()
|
||||
})
|
||||
.catch((response) => response.json()
|
||||
.then((body) => done.fail(JSON.stringify(body)))
|
||||
)
|
||||
})
|
||||
|
||||
it('should error if not authenticated.', (done) => {
|
||||
keystone
|
||||
.catalogList()
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Internap.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import config from './helpers/cloudsConfig'
|
||||
import Version from '../../src/util/version'
|
||||
import Neutron from '../../src/neutron'
|
||||
import Keystone from '../../src/keystone'
|
||||
import log from 'loglevel'
|
||||
|
||||
log.setLevel('DEBUG')
|
||||
|
||||
describe('neutron', () => {
|
||||
// Create a keystone instance and extract the neutron API endpoint.
|
||||
const devstackConfig = config.clouds.devstack
|
||||
const keystone = new Keystone(devstackConfig)
|
||||
const tokenPromise = keystone.tokenIssue()
|
||||
|
||||
const configPromise = tokenPromise
|
||||
.then((token) => keystone.catalogList(token))
|
||||
.then((catalog) => catalog.find((entry) => entry.name === 'neutron'))
|
||||
.then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public'))
|
||||
|
||||
describe('versions()', () => {
|
||||
it('should return a list of all versions available on this clouds\' neutron', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Neutron(config))
|
||||
.then((neutron) => neutron.versions())
|
||||
.then((versions) => {
|
||||
// Quick sanity check.
|
||||
expect(versions.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('version()', () => {
|
||||
/**
|
||||
* This test acts as a canary, to inform the SDK developers that the Neutron API
|
||||
* has changed in a significant way.
|
||||
*/
|
||||
it('should return a supported version.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Neutron(config))
|
||||
.then((neutron) => neutron.version())
|
||||
.then((apiVersion) => {
|
||||
expect(apiVersion instanceof Version).not.toBeFalsy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('networkList()', () => {
|
||||
it('should return the networks as an array.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Neutron(config))
|
||||
.then((neutron) => neutron.networkList(tokenPromise))
|
||||
.then((networks) => {
|
||||
expect(networks.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Michael Krotscheck.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import config from './helpers/cloudsConfig'
|
||||
import Version from '../../src/util/version'
|
||||
import Nova from '../../src/nova'
|
||||
import Keystone from '../../src/keystone'
|
||||
import log from 'loglevel'
|
||||
|
||||
log.setLevel('DEBUG')
|
||||
|
||||
describe('Nova', () => {
|
||||
// Create a keystone instance and extract the nova API endpoint.
|
||||
const devstackConfig = config.clouds.devstack
|
||||
const keystone = new Keystone(devstackConfig)
|
||||
const tokenPromise = keystone.tokenIssue()
|
||||
|
||||
const configPromise = tokenPromise
|
||||
.then((token) => keystone.catalogList(token))
|
||||
.then((catalog) => catalog.find((entry) => entry.name === 'nova'))
|
||||
.then((entry) => entry.endpoints.find((endpoint) => endpoint.interface === 'public'))
|
||||
|
||||
describe('version()', () => {
|
||||
/**
|
||||
* This test acts as a canary, to inform the SDK developers that the Nova API
|
||||
* has changed in a significant way.
|
||||
*/
|
||||
it('should return a supported version.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Nova(config))
|
||||
.then((nova) => nova.version())
|
||||
.then((apiVersion) => {
|
||||
expect(apiVersion instanceof Version).not.toBeFalsy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('flavorList()', () => {
|
||||
/**
|
||||
* Assert that we can get a list of flavors.
|
||||
*/
|
||||
it('should return a list of flavors.', (done) => {
|
||||
configPromise
|
||||
.then((config) => new Nova(config))
|
||||
.then((nova) => nova.flavorList(tokenPromise))
|
||||
.then((flavors) => {
|
||||
expect(flavors.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,48 +0,0 @@
|
|||
import config from './helpers/cloudsConfig'
|
||||
import OpenStack from '../../src/openstack'
|
||||
import log from 'loglevel'
|
||||
|
||||
log.setLevel('DEBUG')
|
||||
|
||||
describe('OpenStack', () => {
|
||||
const 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))
|
||||
})
|
||||
})
|
||||
|
||||
describe('imageList()', () => {
|
||||
it('should return the images as an array.', (done) => {
|
||||
const openstack = new OpenStack(devstackConfig)
|
||||
|
||||
openstack.imageList()
|
||||
.then((images) => {
|
||||
expect(images.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('flavorList()', () => {
|
||||
it('should return the flavors as an array.', (done) => {
|
||||
const openstack = new OpenStack(devstackConfig)
|
||||
|
||||
openstack.flavorList()
|
||||
.then((flavors) => {
|
||||
expect(flavors.length > 0).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,5 +0,0 @@
|
|||
import Jasmine from 'jasmine'
|
||||
|
||||
const jasmine = new Jasmine()
|
||||
jasmine.loadConfigFile('test/functional/jasmine.json')
|
||||
jasmine.execute()
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Glance from '../../src/glance.js'
|
||||
import * as mockData from './helpers/data/glance'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('Glance', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should export a class', () => {
|
||||
const glance = new Glance(mockData.config)
|
||||
expect(glance).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw an error for an empty config', () => {
|
||||
expect(() => new Glance()).toThrow()
|
||||
})
|
||||
|
||||
describe('serviceEndpoint()', () => {
|
||||
it('Should return a valid endpoint to the glance API.', (done) => {
|
||||
const glance = new Glance(mockData.config)
|
||||
|
||||
fetchMock.mock(mockData.root())
|
||||
|
||||
glance.serviceEndpoint()
|
||||
.then((endpoint) => {
|
||||
expect(endpoint).toEqual('http://192.168.99.99:9292/v2/')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('imageList()', () => {
|
||||
let glance = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.root())
|
||||
glance = new Glance(mockData.config)
|
||||
})
|
||||
|
||||
it('should return the images as an array.', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
fetchMock.mock(mockData.imageList(token))
|
||||
glance
|
||||
.imageList(token)
|
||||
.then((images) => {
|
||||
expect(images.length).not.toBe(0)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
const mockOptions = mockData.imageList(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
glance
|
||||
.imageList(token)
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return glance.imageList(token)
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,204 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* keystone. Most of these are functions, as FetchMock does not perform a safe clone of the
|
||||
* instances, and may accidentally modify them at runtime.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock cloud configuration that matches our test data below. This is not a full clouds.yaml
|
||||
* format, rather just the subsection pointing to a particular cloud.
|
||||
*/
|
||||
const glanceConfig = {
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9292/',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '0b8b5f0f14904136ab5a4f83f27ec49a'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new FetchMock configuration for the root endpoint.
|
||||
*
|
||||
* @returns {{}} A full FetchMock configuration for Glance's Root Resource.
|
||||
*/
|
||||
function rootResponse () {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:9292/',
|
||||
response: {
|
||||
versions: [
|
||||
{
|
||||
status: 'CURRENT',
|
||||
id: 'v2.5',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v2/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.3',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v2/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.2',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v2/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.1',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v2/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.0',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v2/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v1.1',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v1/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v1.0',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9292/v1/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function imageList (token) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:9292/v2/images',
|
||||
headers: {
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
response: {
|
||||
images: [
|
||||
{
|
||||
status: 'active',
|
||||
name: 'cirros-0.3.4-x86_64-uec',
|
||||
tags: [],
|
||||
kernel_id: '7c26de84-1ad7-4851-aea5-5c173d0605c8',
|
||||
container_format: 'ami',
|
||||
created_at: '2016-08-26T17:16:10Z',
|
||||
ramdisk_id: '3ac21034-3764-407a-baab-966db753e3e5',
|
||||
disk_format: 'ami',
|
||||
updated_at: '2016-08-26T17:16:10Z',
|
||||
visibility: 'public',
|
||||
self: '/v2/images/8f3c7c9a-d812-46b1-9223-aa2d8f12c10a',
|
||||
min_disk: 0,
|
||||
protected: false,
|
||||
id: '8f3c7c9a-d812-46b1-9223-aa2d8f12c10a',
|
||||
size: 25165824,
|
||||
file: '/v2/images/8f3c7c9a-d812-46b1-9223-aa2d8f12c10a/file',
|
||||
checksum: 'eb9139e4942121f22bbc2afc0400b2a4',
|
||||
owner: 'fcfe212681764e0595df8df83fd019f6',
|
||||
virtual_size: null,
|
||||
min_ram: 0,
|
||||
schema: '/v2/schemas/image'
|
||||
},
|
||||
{
|
||||
status: 'active',
|
||||
name: 'cirros-0.3.4-x86_64-uec-ramdisk',
|
||||
tags: [],
|
||||
container_format: 'ari',
|
||||
created_at: '2016-08-26T17:16:09Z',
|
||||
size: 3740163,
|
||||
disk_format: 'ari',
|
||||
updated_at: '2016-08-26T17:16:09Z',
|
||||
visibility: 'public',
|
||||
self: '/v2/images/3ac21034-3764-407a-baab-966db753e3e5',
|
||||
min_disk: 0,
|
||||
protected: false,
|
||||
id: '3ac21034-3764-407a-baab-966db753e3e5',
|
||||
file: '/v2/images/3ac21034-3764-407a-baab-966db753e3e5/file',
|
||||
checksum: 'be575a2b939972276ef675752936977f',
|
||||
owner: 'fcfe212681764e0595df8df83fd019f6',
|
||||
virtual_size: null,
|
||||
min_ram: 0,
|
||||
schema: '/v2/schemas/image'
|
||||
},
|
||||
{
|
||||
status: 'active',
|
||||
name: 'cirros-0.3.4-x86_64-uec-kernel',
|
||||
tags: [],
|
||||
container_format: 'aki',
|
||||
created_at: '2016-08-26T17:16:08Z',
|
||||
size: 4979632,
|
||||
disk_format: 'aki',
|
||||
updated_at: '2016-08-26T17:16:08Z',
|
||||
visibility: 'public',
|
||||
self: '/v2/images/7c26de84-1ad7-4851-aea5-5c173d0605c8',
|
||||
min_disk: 0,
|
||||
protected: false,
|
||||
id: '7c26de84-1ad7-4851-aea5-5c173d0605c8',
|
||||
file: '/v2/images/7c26de84-1ad7-4851-aea5-5c173d0605c8/file',
|
||||
checksum: '8a40c862b5735975d82605c1dd395796',
|
||||
owner: 'fcfe212681764e0595df8df83fd019f6',
|
||||
virtual_size: null,
|
||||
min_ram: 0,
|
||||
schema: '/v2/schemas/image'
|
||||
}],
|
||||
schema: '/v2/schemas/images',
|
||||
first: '/v2/images'
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
glanceConfig as config,
|
||||
rootResponse as root,
|
||||
imageList
|
||||
}
|
|
@ -1,453 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* keystone. Most of these are functions, as FetchMock does not perform a safe clone of the
|
||||
* instances, and may accidentally modify them at runtime.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock cloud configuration that matches our test data below. This is not a full clouds.yaml
|
||||
* format, rather just the subsection pointing to a particular cloud.
|
||||
*/
|
||||
const cloudConfig = {
|
||||
region_name: 'Region1',
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
project_name: 'js-openstack-lib',
|
||||
auth_url: 'http://192.168.99.99/'
|
||||
}
|
||||
}
|
||||
|
||||
const catalogListData = [
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99/identity_v2_admin',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: '940664e070864b638dfafc53cfcbe887'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99/identity',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: 'c3707565bccb407c888040fa9b7e77b0'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99/identity',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: 'fb28f261810449ea98b2df646b847a74'
|
||||
}
|
||||
],
|
||||
type: 'identity',
|
||||
id: '0599684d07a145659fa858c1deb4e885',
|
||||
name: 'keystone'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v3/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '611a5108ef0b4f999ad439b0e1abd9da'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v3/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: 'ae08047e33d848c8b1c77f99bc572e22'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v3/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: 'e26c6757baa549469772e16e03c051b8'
|
||||
}
|
||||
],
|
||||
type: 'volumev3',
|
||||
id: '1092f88a41c64fc7b0331fce96e7df6c',
|
||||
name: 'cinderv3'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v1/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '14ad1642b0874816a7ff08eb0e24be87'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v1/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '8bb7b28802d44e9d80fbb358a3e133af'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v1/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: 'c271745ff29c4c9d829ab3187d41cab7'
|
||||
}
|
||||
],
|
||||
type: 'volume',
|
||||
id: '5067360b6f264558945b7d2c312dd126',
|
||||
name: 'cinder'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9292',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: '0b8b5f0f14904136ab5a4f83f27ec49a'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9292',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '97c90e43e1fe473b85ef47627006dcdd'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9292',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: 'ee114418c77a45d2a3cc28240dc4281d'
|
||||
}
|
||||
],
|
||||
type: 'image',
|
||||
id: '6512ca68fbd543928768201198cd7e42',
|
||||
name: 'glance'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2.1',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '14129d81da0e44abae0c082c535b58cc'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2.1',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: 'be681632633d4a62a781148c2fedd6aa'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2.1',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: 'f8979efb0903442a9068d57fce4eafb2'
|
||||
}
|
||||
],
|
||||
type: 'compute',
|
||||
id: '6d3dd68ae2224fd39503342220b5d2c2',
|
||||
name: 'nova'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: '308f5ed663a7417db3f078f7e3b66db8'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '9f08e41e8156498ba01b5cc83cc9e1da'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: 'b855d4c048f1468f9df5a9950ae811c6'
|
||||
}
|
||||
],
|
||||
type: 'compute_legacy',
|
||||
id: '8ca07a04d03145a094c404b5edf70c18',
|
||||
name: 'nova_legacy'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '2b6e28e0aade41b5b80baa9012e54ca4'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: '79c96252a8ab4c7181ef4fe97237c314'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8776/v2/8b2aa635109f4d0ab355e18a269d341f',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '8d4cbc86845a4ecb90f19903636205a7'
|
||||
}
|
||||
],
|
||||
type: 'volumev2',
|
||||
id: 'a7967e90d1044b1fa6d80b033f1da510',
|
||||
name: 'cinderv2'
|
||||
},
|
||||
{
|
||||
endpoints: [
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9696/',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '7033fa4ebed74e3fa51753162150a1f2'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9696/',
|
||||
region: 'RegionOne',
|
||||
interface: 'internal',
|
||||
id: '7aa942d402a34d4c90454b9d84285855'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9696/',
|
||||
region: 'RegionOne',
|
||||
interface: 'admin',
|
||||
id: 'bd8db1bafe41489bbbc45641e525ee7d'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionTwo',
|
||||
url: 'http://192.168.99.100:9696/',
|
||||
region: 'RegionTwo',
|
||||
interface: 'public',
|
||||
id: '7033fa4ebed74e3fa51753162150a1f2'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionTwo',
|
||||
url: 'http://192.168.99.100:9696/',
|
||||
region: 'RegionOne',
|
||||
interface: 'RegionTwo',
|
||||
id: '7aa942d402a34d4c90454b9d84285855'
|
||||
},
|
||||
{
|
||||
region_id: 'RegionTwo',
|
||||
url: 'http://192.168.99.100:9696/',
|
||||
region: 'RegionTwo',
|
||||
interface: 'admin',
|
||||
id: 'bd8db1bafe41489bbbc45641e525ee7d'
|
||||
}
|
||||
],
|
||||
type: 'network',
|
||||
id: 'f36b9e68ef114769b85024513ee61047',
|
||||
name: 'neutron'
|
||||
}
|
||||
]
|
||||
|
||||
const tokenInfoData = {
|
||||
is_domain: false,
|
||||
methods: [
|
||||
'password'
|
||||
],
|
||||
roles: [
|
||||
{
|
||||
id: 'cfa75a8719f544e2903e5899785b0cf0',
|
||||
name: 'anotherrole'
|
||||
},
|
||||
{
|
||||
id: '5f8126fad6704a999a3651955c7d8219',
|
||||
name: 'Member'
|
||||
}
|
||||
],
|
||||
is_admin_project: false,
|
||||
project: {
|
||||
domain: {
|
||||
id: 'default',
|
||||
name: 'Default'
|
||||
},
|
||||
id: '8b2aa635109f4d0ab355e18a269d341f',
|
||||
name: 'demo'
|
||||
},
|
||||
catalog: catalogListData,
|
||||
expires_at: '2016-08-19T18:04:11.157434Z',
|
||||
user: {
|
||||
domain: {
|
||||
id: 'default',
|
||||
name: 'Default'
|
||||
},
|
||||
id: 'd56a64f45da0450a826ede637be64304',
|
||||
name: 'demo'
|
||||
},
|
||||
audit_ids: [
|
||||
'FtgqCjtuR2-V36loBJ8mxQ'
|
||||
],
|
||||
issued_at: '2016-08-19T17:04:11.157456Z'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new FetchMock configuration for the root endpoint.
|
||||
*
|
||||
* @returns {{}} A full FetchMock configuration for Keystone's Root Resource.
|
||||
*/
|
||||
function rootResponse () {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99/',
|
||||
response: {
|
||||
versions: {
|
||||
values: [
|
||||
{
|
||||
status: 'stable',
|
||||
updated: '2016-10-06T00:00:00Z',
|
||||
'media-types': [
|
||||
{
|
||||
base: 'application/json',
|
||||
type: 'application/vnd.openstack.identity-v3+json'
|
||||
}
|
||||
],
|
||||
id: 'v3.7',
|
||||
links: [
|
||||
{
|
||||
href: 'http://docs.openstack.org/',
|
||||
type: 'text/html',
|
||||
rel: 'describedby'
|
||||
},
|
||||
{
|
||||
href: 'http://192.168.99.99/identity_v2_admin/v3/',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'deprecated',
|
||||
updated: '2016-08-04T00:00:00Z',
|
||||
'media-types': [
|
||||
{
|
||||
base: 'application/json',
|
||||
type: 'application/vnd.openstack.identity-v2.0+json'
|
||||
}
|
||||
],
|
||||
id: 'v2.0',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99/identity_v2_admin/v2.0/',
|
||||
rel: 'self'
|
||||
},
|
||||
{
|
||||
href: 'http://docs.openstack.org/',
|
||||
type: 'text/html',
|
||||
rel: 'describedby'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenIssue () {
|
||||
return {
|
||||
method: 'POST',
|
||||
matcher: 'http://192.168.99.99/identity_v2_admin/v3/auth/tokens',
|
||||
response: {
|
||||
status: 201,
|
||||
headers: {
|
||||
'X-Subject-Token': 'test_token'
|
||||
},
|
||||
body: {
|
||||
token: tokenInfoData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenRevoke (token, adminToken = null) {
|
||||
return {
|
||||
method: 'DELETE',
|
||||
matcher: 'http://192.168.99.99/identity_v2_admin/v3/auth/tokens',
|
||||
headers: {
|
||||
'X-Subject-Token': token,
|
||||
'X-Auth-Token': adminToken || token
|
||||
},
|
||||
response: {
|
||||
status: 204
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenInfo (token) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99/identity_v2_admin/v3/auth/tokens',
|
||||
response: {
|
||||
status: 200,
|
||||
headers: {
|
||||
'X-Subject-Token': token
|
||||
},
|
||||
body: {
|
||||
token: tokenInfoData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function catalogList (token) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99/identity_v2_admin/v3/auth/catalog',
|
||||
headers: {
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
response: {
|
||||
catalog: catalogListData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
cloudConfig as config,
|
||||
rootResponse as root,
|
||||
tokenIssue,
|
||||
tokenRevoke,
|
||||
tokenInfo,
|
||||
catalogList
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Internap.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* neutron. Most of these are functions, as FetchMock does not perform a safe clone of the
|
||||
* instances, and may accidentally modify them at runtime.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock cloud configuration that matches our test data below. This is not a clouds.yaml
|
||||
* format but a subsection of service endpoint return by keystone's catalog.
|
||||
*/
|
||||
const neutronConfig = {
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:9696/',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: '0b8b5f0f14904136ab5a4f83f27ec49a'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new FetchMock configuration for the root endpoint.
|
||||
*
|
||||
* @returns {{}} A full FetchMock configuration for Neutron's Root Resource.
|
||||
*/
|
||||
function rootResponse () {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:9696/',
|
||||
response: {
|
||||
versions: [
|
||||
{
|
||||
status: 'CURRENT',
|
||||
id: 'v2.0',
|
||||
links: [
|
||||
{
|
||||
href: 'http://192.168.99.99:9696/v2.0',
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function networkList (token) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:9696/v2.0/networks',
|
||||
headers: {
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
response: {
|
||||
networks: [
|
||||
{
|
||||
status: 'ACTIVE',
|
||||
subnets: [
|
||||
'54d6f61d-db07-451c-9ab3-b9609b6b6f0b'
|
||||
],
|
||||
name: 'private-network',
|
||||
'provider:physical_network': null,
|
||||
admin_state_up: true,
|
||||
tenant_id: '4fd44f30292945e481c7b8a0c8908869',
|
||||
qos_policy_id: '6a8454ade84346f59e8d40665f878b2e',
|
||||
'provider:network_type': 'local',
|
||||
'router:external': true,
|
||||
mtu: 0,
|
||||
shared: true,
|
||||
id: 'd32019d3-bc6e-4319-9c1d-6722fc136a22',
|
||||
'provider:segmentation_id': null
|
||||
},
|
||||
{
|
||||
status: 'ACTIVE',
|
||||
subnets: [
|
||||
'08eae331-0402-425a-923c-34f7cfe39c1b'
|
||||
],
|
||||
name: 'private',
|
||||
'provider:physical_network': null,
|
||||
admin_state_up: true,
|
||||
tenant_id: '26a7980765d0414dbc1fc1f88cdb7e6e',
|
||||
qos_policy_id: 'bfdb6c39f71e4d44b1dfbda245c50819',
|
||||
'provider:network_type': 'local',
|
||||
'router:external': true,
|
||||
mtu: 0,
|
||||
shared: true,
|
||||
id: 'db193ab3-96e3-4cb3-8fc5-05f4296d0324',
|
||||
'provider:segmentation_id': null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
neutronConfig as config,
|
||||
rootResponse as root,
|
||||
networkList
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* nova. Most of these are functions, as FetchMock does not perform a safe clone of the
|
||||
* instances, and may accidentally modify them at runtime.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A catalog entry that matches what we expect from the Keystone Catalog for nova compute.
|
||||
*/
|
||||
const novaConfig = {
|
||||
region_id: 'RegionOne',
|
||||
url: 'http://192.168.99.99:8774/v2.1',
|
||||
region: 'RegionOne',
|
||||
interface: 'public',
|
||||
id: 'be681632633d4a62a781148c2fedd6aa'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new FetchMock configuration for the root endpoint.
|
||||
*
|
||||
* @returns {{}} A full FetchMock configuration for Nova's Root Resource.
|
||||
*/
|
||||
function rootResponse () {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:8774/',
|
||||
response: {
|
||||
versions: [{
|
||||
status: 'CURRENT',
|
||||
updated: '2013-07-23T11:33:21Z',
|
||||
links: [{ href: 'http://192.168.99.99:8774/v2.1/', rel: 'self' }],
|
||||
min_version: '2.1',
|
||||
version: '2.38',
|
||||
id: 'v2.1'
|
||||
}, {
|
||||
status: 'SUPPORTED',
|
||||
updated: '2011-01-21T11:33:21Z',
|
||||
links: [{ href: 'http://192.168.99.99:8774/v2/', rel: 'self' }],
|
||||
min_version: '',
|
||||
version: '',
|
||||
id: 'v2.0'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FAILING response to the version endpoint.
|
||||
*
|
||||
* @param {String} version The version ID.
|
||||
* @return {{}} A FetchMock configuration for this request's response.
|
||||
*/
|
||||
function versionedRootResponse (version = 'v2.1') {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: `http://192.168.99.99:8774/${version}`,
|
||||
response: {
|
||||
status: 401
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate an imageList response.
|
||||
*
|
||||
* @param {String} token An auth token.
|
||||
* @return {{}} A FetchMock configuration for this request's response.
|
||||
*/
|
||||
function flavorList (token) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: 'http://192.168.99.99:8774/v2.1/flavors',
|
||||
headers: {
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
response: {
|
||||
flavors: [{
|
||||
id: '1',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/1', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/1', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.tiny'
|
||||
}, {
|
||||
id: '2',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/2', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/2', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.small'
|
||||
}, {
|
||||
id: '3',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/3', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/3', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.medium'
|
||||
}, {
|
||||
id: '4',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/4', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/4', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.large'
|
||||
}, {
|
||||
id: '42',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/42', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/42', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.nano'
|
||||
}, {
|
||||
id: '5',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/5', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/5', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.xlarge'
|
||||
}, {
|
||||
id: '84',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/84', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/84', rel: 'bookmark' }
|
||||
],
|
||||
name: 'm1.micro'
|
||||
}, {
|
||||
id: 'c1',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/c1', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/c1', rel: 'bookmark' }
|
||||
],
|
||||
name: 'cirros256'
|
||||
}, {
|
||||
id: 'd1',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/d1', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/d1', rel: 'bookmark' }
|
||||
],
|
||||
name: 'ds512M'
|
||||
}, {
|
||||
id: 'd2',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/d2', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/d2', rel: 'bookmark' }
|
||||
],
|
||||
name: 'ds1G'
|
||||
}, {
|
||||
id: 'd3',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/d3', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/d3', rel: 'bookmark' }
|
||||
],
|
||||
name: 'ds2G'
|
||||
}, {
|
||||
id: 'd4',
|
||||
links: [
|
||||
{ href: 'http://192.168.99.99:8774/v2.1/flavors/d4', rel: 'self' },
|
||||
{ href: 'http://192.168.99.99:8774/flavors/d4', rel: 'bookmark' }
|
||||
],
|
||||
name: 'ds4G'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
novaConfig as config,
|
||||
rootResponse as root,
|
||||
versionedRootResponse as rootVersion,
|
||||
flavorList
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* openstack class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock cloud configuration that matches our test data below. This is not a full clouds.yaml
|
||||
* format, rather just the subsection pointing to a particular cloud.
|
||||
* @param {String} regionName A region name to use
|
||||
* @returns {{}} a cloud config object.
|
||||
*/
|
||||
function cloudConfig (regionName = 'RegionOne') {
|
||||
return {
|
||||
region_name: regionName,
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
project_name: 'js-openstack-lib',
|
||||
auth_url: 'http://192.168.99.99/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
cloudConfig as config
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Michael Krotscheck.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file contains test data for fetchMock, to simplify bootstrapping of unit tests for
|
||||
* cross-service version detection.
|
||||
*/
|
||||
|
||||
/**
|
||||
* URLs to match the test data below.
|
||||
*/
|
||||
const rootUrl = 'http://example.com/'
|
||||
const subUrl = `${rootUrl}v1`
|
||||
|
||||
/**
|
||||
* A mock list of supported versions for the below requests.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
const versions = [
|
||||
'v2.3'
|
||||
]
|
||||
|
||||
/**
|
||||
* Build a new FetchMock configuration for the versions (root) endpoint.
|
||||
*
|
||||
* @returns {{}} A full FetchMock configuration for a versions resource.
|
||||
*/
|
||||
function rootResponse () {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: rootUrl,
|
||||
response: {
|
||||
versions: [
|
||||
{
|
||||
status: 'CURRENT',
|
||||
id: 'v2.3',
|
||||
links: [
|
||||
{
|
||||
href: `${rootUrl}v2/`,
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.2',
|
||||
links: [
|
||||
{
|
||||
href: `${rootUrl}v2/`,
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v2.1',
|
||||
links: [
|
||||
{
|
||||
href: `${rootUrl}v2/`,
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v1.1',
|
||||
links: [
|
||||
{
|
||||
href: `${rootUrl}v1/`,
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
status: 'SUPPORTED',
|
||||
id: 'v1.0',
|
||||
links: [
|
||||
{
|
||||
href: `${rootUrl}v1/`,
|
||||
rel: 'self'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FetchMock configuration for a 401 response against the versioned API url.
|
||||
*
|
||||
* @param {int} httpStatus The HTTP status for the response.
|
||||
* @returns {{}} A full FetchMock configuration a failing request..
|
||||
*/
|
||||
function subResponse (httpStatus = 401) {
|
||||
return {
|
||||
method: 'GET',
|
||||
matcher: subUrl,
|
||||
response: {
|
||||
status: httpStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
rootUrl,
|
||||
subUrl,
|
||||
versions,
|
||||
rootResponse,
|
||||
subResponse
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"spec_dir": "test/unit",
|
||||
"spec_files": [
|
||||
"**/*[tT]est.js"
|
||||
],
|
||||
"helpers": [
|
||||
"../../node_modules/babel-register/lib/node.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
|
@ -1,330 +0,0 @@
|
|||
import Keystone from '../../src/keystone.js'
|
||||
import * as mockData from './helpers/data/keystone'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('Keystone', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should export a class', () => {
|
||||
const keystone = new Keystone(mockData.config)
|
||||
expect(keystone).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw an error for an empty config', () => {
|
||||
expect(() => new Keystone()).toThrow()
|
||||
})
|
||||
|
||||
describe('serviceEndpoint()', () => {
|
||||
it('Should return a valid endpoint to the keystone API.', (done) => {
|
||||
const keystone = new Keystone(mockData.config)
|
||||
|
||||
fetchMock.mock(mockData.root())
|
||||
|
||||
keystone.serviceEndpoint()
|
||||
.then((endpoint) => {
|
||||
expect(endpoint).toEqual('http://192.168.99.99/identity_v2_admin/v3/')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenIssue()', () => {
|
||||
it('should "just work" by using provided credentials from the config.', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
expect(token).toEqual('test_token') // From mock data
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a user ID', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const userId = 'userId'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
user_id: userId
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.identity.password.user.id).toEqual(userId)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a username and a user domain ID', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const username = 'username'
|
||||
const userDomainId = 'userDomainId'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
username: username,
|
||||
user_domain_id: userDomainId
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.identity.password.user.name).toEqual(username)
|
||||
expect(requestBody.auth.identity.password.user.domain.id).toEqual(userDomainId)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a username and a user domain name', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const username = 'username'
|
||||
const userDomainName = 'userDomainName'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
username: username,
|
||||
user_domain_name: userDomainName
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.identity.password.user.name).toEqual(username)
|
||||
expect(requestBody.auth.identity.password.user.domain.name).toEqual(userDomainName)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a project ID', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const projectId = 'projectId'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
project_id: projectId
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.scope.project.id).toEqual(projectId)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a project name and a project domain ID', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const projectName = 'projectName'
|
||||
const projectDomainId = 'projectDomainId'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
project_name: projectName,
|
||||
project_domain_id: projectDomainId
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.scope.project.name).toEqual(projectName)
|
||||
expect(requestBody.auth.scope.project.domain.id).toEqual(projectDomainId)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should support authentication with a project name and a project domain name', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const projectName = 'projectName'
|
||||
const projectDomainName = 'projectDomainName'
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue({
|
||||
project_name: projectName,
|
||||
project_domain_name: projectDomainName
|
||||
})
|
||||
.then(() => {
|
||||
const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body)
|
||||
expect(requestBody.auth.scope.project.name).toEqual(projectName)
|
||||
expect(requestBody.auth.scope.project.domain.name).toEqual(projectDomainName)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const mockOptions = mockData.tokenIssue()
|
||||
fetchMock.mock(mockData.root())
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
const keystone = new Keystone(mockData.config)
|
||||
keystone
|
||||
.tokenIssue()
|
||||
.then((token) => {
|
||||
expect(token).toEqual('test_token') // From mock data
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return keystone.tokenIssue()
|
||||
})
|
||||
.then((token) => {
|
||||
expect(token).toEqual('test_token') // From mock data
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenRevoke()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.root())
|
||||
keystone = new Keystone(mockData.config)
|
||||
})
|
||||
|
||||
it('should return a 204 response on a valid revocation.', (done) => {
|
||||
const token = 'test_token'
|
||||
const adminToken = 'test_admin_token'
|
||||
|
||||
fetchMock.mock(mockData.tokenRevoke(token, adminToken))
|
||||
keystone
|
||||
.tokenRevoke(token, adminToken)
|
||||
.then((response) => {
|
||||
expect(response.status).toEqual(204) // From mock data
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
const mockOptions = mockData.tokenRevoke(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
keystone
|
||||
.tokenRevoke(token)
|
||||
.then((response) => {
|
||||
expect(response.status).toEqual(204)
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
|
||||
// Yes, I realize that this should actually return an error since the token is no
|
||||
// longer valid, but we're testing for promise caching here, not valid http flow.
|
||||
return keystone.tokenRevoke(token)
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.status).toEqual(204)
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenInfo()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.root())
|
||||
keystone = new Keystone(mockData.config)
|
||||
})
|
||||
|
||||
const token = 'test_token'
|
||||
|
||||
it('should return information about a token', (done) => {
|
||||
fetchMock.mock(mockData.tokenInfo(token))
|
||||
keystone
|
||||
.tokenInfo(token)
|
||||
.then((info) => {
|
||||
expect(info.token).toBeDefined()
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const mockOptions = mockData.tokenInfo(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
keystone
|
||||
.tokenInfo(token)
|
||||
.then((info) => {
|
||||
expect(info.token).toBeDefined()
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return keystone.tokenInfo(token)
|
||||
})
|
||||
.then((info) => {
|
||||
expect(info.token).toBeDefined()
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('catalogList()', () => {
|
||||
let keystone = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.root())
|
||||
keystone = new Keystone(mockData.config)
|
||||
})
|
||||
|
||||
it('should return the catalog as an array.', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
fetchMock.mock(mockData.catalogList(token))
|
||||
keystone
|
||||
.catalogList(token)
|
||||
.then((catalog) => {
|
||||
expect(catalog.length).not.toBe(0)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
const mockOptions = mockData.catalogList(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
keystone
|
||||
.catalogList(token)
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return keystone.catalogList(token)
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Internap.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Neutron from '../../src/neutron.js'
|
||||
import * as mockData from './helpers/data/neutron'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('neutron', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should export a class', () => {
|
||||
const neutron = new Neutron(mockData.config)
|
||||
expect(neutron).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw an error for an empty config', () => {
|
||||
try {
|
||||
const neutron = new Neutron()
|
||||
neutron.versions()
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('An endpoint configuration is required.')
|
||||
}
|
||||
})
|
||||
|
||||
describe('networkList()', () => {
|
||||
let neutron = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.root())
|
||||
neutron = new Neutron(mockData.config)
|
||||
})
|
||||
|
||||
it('should return the networks as an array.', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
fetchMock.mock(mockData.networkList(token))
|
||||
neutron
|
||||
.networkList(token)
|
||||
.then((networks) => {
|
||||
expect(networks.length).toBe(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
const mockOptions = mockData.networkList(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
neutron
|
||||
.networkList(token)
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return neutron.networkList(token)
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Michael Krotscheck.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Nova from '../../src/nova.js'
|
||||
import * as mockData from './helpers/data/nova'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('Nova', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should export a class', () => {
|
||||
const nova = new Nova(mockData.config)
|
||||
expect(nova).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw an error for an empty config', () => {
|
||||
expect(() => new Nova(null)).toThrow()
|
||||
})
|
||||
|
||||
describe('flavorList()', () => {
|
||||
let nova = null
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mock(mockData.rootVersion())
|
||||
fetchMock.mock(mockData.root())
|
||||
nova = new Nova(mockData.config)
|
||||
})
|
||||
|
||||
it('should return the flavors as an array.', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
fetchMock.mock(mockData.flavorList(token))
|
||||
nova
|
||||
.flavorList(token)
|
||||
.then((images) => {
|
||||
expect(images.length).not.toBe(0)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not cache its results', (done) => {
|
||||
const token = 'test_token'
|
||||
|
||||
const mockOptions = mockData.flavorList(token)
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
nova
|
||||
.flavorList(token)
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return nova.flavorList(token)
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,288 +0,0 @@
|
|||
import OpenStack from '../../src/openstack'
|
||||
import * as openStackMockData from './helpers/data/openstack'
|
||||
import * as neutronMockData from './helpers/data/neutron'
|
||||
import * as keystoneMockData from './helpers/data/keystone'
|
||||
import * as glanceMockData from './helpers/data/glance'
|
||||
import * as novaMockData from './helpers/data/nova'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import Neutron from '../../src/neutron'
|
||||
import Keystone from '../../src/keystone'
|
||||
import Glance from '../../src/glance'
|
||||
import Nova from '../../src/nova'
|
||||
|
||||
describe('Simple test', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should export a class', () => {
|
||||
const t = new OpenStack(openStackMockData.config())
|
||||
expect(t).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw an error for an empty config', () => {
|
||||
try {
|
||||
const t = new OpenStack()
|
||||
t.getConfig()
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('A configuration is required.')
|
||||
}
|
||||
})
|
||||
|
||||
it('getConfig should returns the config', () => {
|
||||
const openstack = new OpenStack(openStackMockData.config())
|
||||
const config = openstack.getConfig()
|
||||
expect(config.region_name).toEqual('RegionOne')
|
||||
})
|
||||
|
||||
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('imageList', () => {
|
||||
it('should fetch imageList from glance', (done) => {
|
||||
const openstack = new OpenStack(openStackMockData.config())
|
||||
const glance = mockGlance(openstack)
|
||||
const imagesData = glanceMockData.imageList('token').response.images
|
||||
|
||||
spyOn(glance, 'imageList').and.returnValue(Promise.resolve(imagesData))
|
||||
|
||||
openstack.imageList()
|
||||
.then((images) => {
|
||||
expect(images.length).toBe(3)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('flavorList', () => {
|
||||
it('should fetch flavorList from nova', (done) => {
|
||||
const openstack = new OpenStack(openStackMockData.config())
|
||||
const nova = mockNova(openstack)
|
||||
const flavorsData = novaMockData.flavorList('token').response.flavors
|
||||
|
||||
spyOn(nova, 'flavorList').and.returnValue(Promise.resolve(flavorsData))
|
||||
|
||||
openstack.flavorList()
|
||||
.then((flavors) => {
|
||||
expect(flavors.length).toBe(12)
|
||||
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('creates Neutron instance for the correct endpoint', (done) => {
|
||||
const token = 'test_token'
|
||||
const openstack = new OpenStack(openStackMockData.config('RegionTwo'))
|
||||
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(neutron).toEqual(jasmine.any(Neutron))
|
||||
expect(neutron.endpointUrl).toEqual('http://192.168.99.100: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('_glance', () => {
|
||||
it('creates Glance 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._glance
|
||||
.then((glance) => {
|
||||
expect(keystone.catalogList).toHaveBeenCalledWith(token)
|
||||
expect(glance).toEqual(jasmine.any(Glance))
|
||||
expect(glance.endpointUrl).toEqual('http://192.168.99.99:9292')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should cache Glance 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._glance
|
||||
.then((glance) => {
|
||||
expect(glance).toEqual(jasmine.any(Glance))
|
||||
expect(fetchMock.calls(tokenIssueMock.matcher).length).toEqual(1)
|
||||
expect(fetchMock.calls(catalogListMock.matcher).length).toEqual(1)
|
||||
return openstack._glance
|
||||
})
|
||||
.then((glance) => {
|
||||
expect(glance).toEqual(jasmine.any(Glance))
|
||||
expect(fetchMock.calls(tokenIssueMock.matcher).length).toEqual(1)
|
||||
expect(fetchMock.calls(catalogListMock.matcher).length).toEqual(1)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('_nova', () => {
|
||||
it('creates Nova 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._nova
|
||||
.then((nova) => {
|
||||
expect(keystone.catalogList).toHaveBeenCalledWith(token)
|
||||
expect(nova).toEqual(jasmine.any(Nova))
|
||||
expect(nova.endpointUrl).toEqual('http://192.168.99.99:8774/v2.1')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should cache Nova 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._nova
|
||||
.then((nova) => {
|
||||
expect(nova).toEqual(jasmine.any(Nova))
|
||||
expect(fetchMock.calls(tokenIssueMock.matcher).length).toEqual(1)
|
||||
expect(fetchMock.calls(catalogListMock.matcher).length).toEqual(1)
|
||||
return openstack._nova
|
||||
})
|
||||
.then((nova) => {
|
||||
expect(nova).toEqual(jasmine.any(Nova))
|
||||
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
|
||||
}
|
||||
|
||||
function mockGlance (openstack) {
|
||||
const glance = new Glance(glanceMockData.config)
|
||||
openstack._glancePromise = Promise.resolve(glance)
|
||||
return glance
|
||||
}
|
||||
|
||||
function mockNova (openstack) {
|
||||
const nova = new Nova(novaMockData.config)
|
||||
openstack._novaPromise = Promise.resolve(nova)
|
||||
return nova
|
||||
}
|
||||
})
|
|
@ -1,5 +0,0 @@
|
|||
import Jasmine from 'jasmine'
|
||||
|
||||
const jasmine = new Jasmine()
|
||||
jasmine.loadConfigFile('test/unit/jasmine.json')
|
||||
jasmine.execute()
|
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Michael Krotscheck.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import AbstractService from '../../../src/util/abstractService'
|
||||
import * as mockData from '../helpers/data/versions'
|
||||
import fetchMock from 'fetch-mock' // Might as well use service
|
||||
|
||||
describe('AbstractService', () => {
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should provide a singleton HTTP instance', () => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
expect(service._http).toBeUndefined()
|
||||
const http1 = service.http
|
||||
expect(service._http).toBeDefined()
|
||||
expect(http1).not.toBeNull()
|
||||
const http2 = service.http
|
||||
expect(http1).toBe(http2)
|
||||
})
|
||||
|
||||
it('should return supported versions', () => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
expect(service.supportedVersions).toEqual(mockData.versions)
|
||||
})
|
||||
|
||||
it('should return an empty array if no versions are configured', () => {
|
||||
const service = new AbstractService(mockData.rootUrl, null)
|
||||
expect(service.supportedVersions).toEqual([])
|
||||
})
|
||||
|
||||
describe('versions()', () => {
|
||||
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(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))
|
||||
})
|
||||
|
||||
// This test catches the case when the service catalog already points
|
||||
// at an API version.
|
||||
it('Should retry at the root URL if a 401 is encountered', (done) => {
|
||||
const service = new AbstractService(mockData.subUrl, mockData.versions)
|
||||
|
||||
fetchMock.mock(mockData.subResponse())
|
||||
fetchMock.mock(mockData.rootResponse())
|
||||
|
||||
service.versions()
|
||||
.then((versions) => {
|
||||
// Quick sanity check.
|
||||
expect(versions.length).toBe(5)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should not retry at the root URL if a different status is encountered', (done) => {
|
||||
const service = new AbstractService(mockData.subUrl, mockData.versions)
|
||||
|
||||
fetchMock.mock(mockData.subResponse(500))
|
||||
|
||||
service.versions()
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('Should NOT cache its results', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
const mockOptions = mockData.rootResponse()
|
||||
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.versions()
|
||||
.then(() => {
|
||||
// Validate that the mock has only been invoked once
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return service.versions()
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('version()', () => {
|
||||
it('Should return a supported version of the service API.', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
fetchMock.mock(mockData.rootResponse())
|
||||
|
||||
service.version()
|
||||
.then((version) => {
|
||||
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))
|
||||
})
|
||||
|
||||
it('Should throw an exception if no supported version is found.', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
// Build an invalid mock object.
|
||||
const mockOptions = mockData.rootResponse()
|
||||
mockOptions.response.versions.shift()
|
||||
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.version()
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('Should NOT cache its results', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
const mockOptions = mockData.rootResponse()
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.version()
|
||||
.then(() => {
|
||||
// Validate that the mock has only been invoked once
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return service.version()
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(2)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
|
||||
describe('serviceEndpoint()', () => {
|
||||
it('Should return a valid endpoint to the API.', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
fetchMock.mock(mockData.rootResponse())
|
||||
|
||||
service.serviceEndpoint()
|
||||
.then((endpoint) => {
|
||||
expect(endpoint).toEqual('http://example.com/v2/')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('Should throw an exception if no endpoint is provided.', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
// Build an exception payload.
|
||||
const mockOptions = mockData.rootResponse()
|
||||
mockOptions.response.versions[0].links = []
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.serviceEndpoint()
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('Should throw an exception if no links array exists.', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
|
||||
// Build an exception payload.
|
||||
const mockOptions = mockData.rootResponse()
|
||||
delete mockOptions.response.versions[0].links
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.serviceEndpoint()
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error).not.toBeNull()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('Should cache its results', (done) => {
|
||||
const service = new AbstractService(mockData.rootUrl, mockData.versions)
|
||||
const mockOptions = mockData.rootResponse()
|
||||
fetchMock.mock(mockOptions)
|
||||
|
||||
service.serviceEndpoint()
|
||||
.then(() => {
|
||||
// Validate that the mock has only been invoked once
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
return service.serviceEndpoint()
|
||||
})
|
||||
.then(() => {
|
||||
expect(fetchMock.calls(mockOptions.matcher).length).toEqual(1)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Http from '../../../src/util/http.js'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('Http', () => {
|
||||
let http
|
||||
const testUrl = 'https://example.com/'
|
||||
const testRequest = { lol: 'cat' }
|
||||
const testResponse = { foo: 'bar' }
|
||||
|
||||
beforeEach(() => {
|
||||
http = new Http()
|
||||
})
|
||||
|
||||
afterEach(fetchMock.restore)
|
||||
|
||||
it('should permit manually constructing requests', (done) => {
|
||||
fetchMock.get(testUrl, testResponse)
|
||||
|
||||
http.httpRequest('GET', testUrl)
|
||||
.then((response) => response.json())
|
||||
.then((body) => {
|
||||
expect(fetchMock.called(testUrl)).toBe(true)
|
||||
expect(body).toEqual(testResponse)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should make GET requests', (done) => {
|
||||
fetchMock.get(testUrl, testResponse)
|
||||
|
||||
http.httpGet(testUrl)
|
||||
.then((response) => response.json())
|
||||
.then((body) => {
|
||||
expect(fetchMock.called(testUrl)).toBe(true)
|
||||
expect(body).toEqual(testResponse)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should make PUT requests', (done) => {
|
||||
fetchMock.put(testUrl, testResponse, testRequest)
|
||||
|
||||
http.httpPut(testUrl, testRequest)
|
||||
.then((response) => response.json())
|
||||
.then((body) => {
|
||||
expect(fetchMock.called(testUrl)).toEqual(true)
|
||||
expect(body).toEqual(testResponse)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should make POST requests', (done) => {
|
||||
fetchMock.post(testUrl, testResponse, testRequest)
|
||||
|
||||
http.httpPost(testUrl, testRequest)
|
||||
.then((response) => response.json())
|
||||
.then((body) => {
|
||||
expect(fetchMock.called(testUrl)).toEqual(true)
|
||||
expect(body).toEqual(testResponse)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should make DELETE requests', (done) => {
|
||||
fetchMock.delete(testUrl, testRequest)
|
||||
|
||||
http.httpDelete(testUrl, testRequest)
|
||||
.then(() => {
|
||||
expect(fetchMock.called(testUrl)).toEqual(true)
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should permit setting default headers', (done) => {
|
||||
http.defaultHeaders['Custom-Header'] = 'Custom-Value'
|
||||
fetchMock.get(testUrl, testResponse)
|
||||
|
||||
http.httpGet(testUrl)
|
||||
.then(() => {
|
||||
const headers = fetchMock.lastOptions().headers
|
||||
expect(headers['Custom-Header']).toEqual('Custom-Value')
|
||||
done()
|
||||
})
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
|
||||
it('should pass exceptions back to the invoker', (done) => {
|
||||
fetchMock.get(testUrl, () => {
|
||||
throw new TypeError() // Example- net::ERR_NAME_NOT_RESOLVED
|
||||
})
|
||||
|
||||
http.httpGet(testUrl)
|
||||
.then((response) => done.fail(response))
|
||||
.catch((error) => {
|
||||
expect(error.stack).toBeDefined()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass failed requests to the catch block.', (done) => {
|
||||
fetchMock.get(testUrl, { status: 500, body: testResponse })
|
||||
|
||||
http.httpGet(testUrl)
|
||||
.then((response) => done.fail(response))
|
||||
.catch((response) => {
|
||||
expect(response.status).toBe(500)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not interfere with mocks that have matching headers.', (done) => {
|
||||
fetchMock.mock({
|
||||
method: 'GET',
|
||||
matcher: testUrl,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
response: testResponse
|
||||
})
|
||||
|
||||
http
|
||||
.httpRequest('GET', testUrl, { 'Content-Type': 'application/json' })
|
||||
.then(() => done())
|
||||
.catch((error) => done.fail(error))
|
||||
})
|
||||
})
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Version from '../../../src/util/version.js'
|
||||
|
||||
describe('Version', () => {
|
||||
it('should parse various header versions', () => {
|
||||
const testVersion = function (args, results) {
|
||||
const v = new Version(...args)
|
||||
expect(v.service).toBe(results[0])
|
||||
expect(v.major).toBe(results[1])
|
||||
expect(v.minor).toBe(results[2])
|
||||
expect(v.patch).toBe(results[3])
|
||||
}
|
||||
|
||||
testVersion(['identity 1.2'], ['identity', 1, 2, 0])
|
||||
testVersion(['identity 0.2'], ['identity', 0, 2, 0])
|
||||
testVersion(['compute 2222222.09'], ['compute', 2222222, 9, 0])
|
||||
testVersion(['compute 03.09'], ['compute', 3, 9, 0])
|
||||
testVersion(['compute 03.0'], ['compute', 3, 0, 0])
|
||||
testVersion(['compute 1'], ['compute', 1, 0, 0])
|
||||
testVersion(['compute 0'], ['compute', 0, 0, 0])
|
||||
testVersion(['v2.1.1'], [null, 2, 1, 1])
|
||||
testVersion(['v2.1'], [null, 2, 1, 0])
|
||||
testVersion(['v2'], [null, 2, 0, 0])
|
||||
testVersion(['v'], [null, 0, 0, 0])
|
||||
testVersion(['v0.2'], [null, 0, 2, 0])
|
||||
testVersion(['2.1.1'], [null, 2, 1, 1])
|
||||
testVersion(['2.1'], [null, 2, 1, 0])
|
||||
testVersion(['2'], [null, 2, 0, 0])
|
||||
testVersion([''], [null, 0, 0, 0])
|
||||
testVersion(['0.2'], [null, 0, 2, 0])
|
||||
testVersion(['compute', 'v2.1.1'], ['compute', 2, 1, 1])
|
||||
testVersion(['compute', 'v2.1'], ['compute', 2, 1, 0])
|
||||
testVersion(['compute', 'v2'], ['compute', 2, 0, 0])
|
||||
testVersion(['compute', 'v'], ['compute', 0, 0, 0])
|
||||
testVersion(['compute', 'v0.2'], ['compute', 0, 2, 0])
|
||||
testVersion(['compute', '2.1.1'], ['compute', 2, 1, 1])
|
||||
testVersion(['compute', '2.1'], ['compute', 2, 1, 0])
|
||||
testVersion(['compute', '2'], ['compute', 2, 0, 0])
|
||||
testVersion(['compute', ''], ['compute', 0, 0, 0])
|
||||
testVersion(['compute', '0.2'], ['compute', 0, 2, 0])
|
||||
|
||||
// Invalid inputs...
|
||||
testVersion([null, null], [null, 0, 0, 0])
|
||||
testVersion([{}, {}], [null, 0, 0, 0])
|
||||
testVersion([null, {}], [null, 0, 0, 0])
|
||||
testVersion([{}, null], [null, 0, 0, 0])
|
||||
})
|
||||
|
||||
it('should test for correct equality', () => {
|
||||
const v1 = new Version('compute', '1.0.0')
|
||||
|
||||
// String tests...
|
||||
expect(v1.equals('compute 1.0.0')).toBe(true)
|
||||
expect(v1.equals('compute 1.0.1')).toBe(false)
|
||||
expect(v1.equals('identity 1.0.0')).toBe(false)
|
||||
|
||||
// Version tests
|
||||
expect(v1.equals(new Version('compute 1.0.0'))).toBe(true)
|
||||
expect(v1.equals(new Version('compute 1.0.1'))).toBe(false)
|
||||
expect(v1.equals(new Version('identity 1.0.0'))).toBe(false)
|
||||
expect(v1.equals(new Version('1.0.0'))).toBe(false)
|
||||
|
||||
// Other tests...
|
||||
expect(v1.equals({})).toBe(false)
|
||||
|
||||
const v2 = new Version('1.0.0')
|
||||
|
||||
// String tests...
|
||||
expect(v2.equals('compute 1.0.0')).toBe(false)
|
||||
expect(v2.equals('compute 1.0.1')).toBe(false)
|
||||
expect(v2.equals('1.0.0')).toBe(true)
|
||||
|
||||
// Version tests
|
||||
expect(v2.equals(new Version('compute 1.0.0'))).toBe(false)
|
||||
expect(v2.equals(new Version('compute 1.0.1'))).toBe(false)
|
||||
expect(v2.equals(new Version('identity 1.0.0'))).toBe(false)
|
||||
expect(v2.equals(new Version('1.0.0'))).toBe(true)
|
||||
|
||||
// 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'
|
||||
})
|
||||
})
|
||||
})
|
65
vagrant.sh
65
vagrant.sh
|
@ -1,65 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
apt-get update
|
||||
apt-get dist-upgrade -y
|
||||
apt-get install -y git
|
||||
mkdir -p /devstack
|
||||
cd /devstack
|
||||
|
||||
# Clone if we have to, else just pull.
|
||||
if [ ! -d .git ]
|
||||
then
|
||||
git clone https://git.openstack.org/openstack-dev/devstack /devstack
|
||||
else
|
||||
git pull
|
||||
fi
|
||||
|
||||
# Make sure the user exists
|
||||
/devstack/tools/create-stack-user.sh
|
||||
chown -R stack:stack /devstack
|
||||
|
||||
# Create some default passwords
|
||||
cat >/devstack/.localrc.password <<EOL
|
||||
DATABASE_PASSWORD=password
|
||||
RABBIT_PASSWORD=password
|
||||
SERVICE_PASSWORD=password
|
||||
ADMIN_PASSWORD=password
|
||||
EOL
|
||||
|
||||
# Add some other settings.
|
||||
cat >/devstack/local.conf <<EOL
|
||||
[[local|localrc]]
|
||||
HOST_IP=192.168.99.99
|
||||
SERVICE_HOST=192.168.99.99
|
||||
RECLONE=True
|
||||
|
||||
#CINDER_BRANCH=milestone-proposed
|
||||
#GLANCE_BRANCH=milestone-proposed
|
||||
#HORIZON_BRANCH=milestone-proposed
|
||||
#KEYSTONE_BRANCH=milestone-proposed
|
||||
#KEYSTONECLIENT_BRANCH=milestone-proposed
|
||||
#NOVA_BRANCH=milestone-proposed
|
||||
#NOVACLIENT_BRANCH=milestone-proposed
|
||||
#NEUTRON_BRANCH=milestone-proposed
|
||||
#SWIFT_BRANCH=milestone-proposed
|
||||
|
||||
[[post-config|\$KEYSTONE_CONF]]
|
||||
[cors]
|
||||
allowed_origin=http://localhost:9876
|
||||
|
||||
[[post-config|\$GLANCE_API_CONF]]
|
||||
[cors]
|
||||
allowed_origin=http://localhost:9876
|
||||
|
||||
[[post-config|\$NEUTRON_CONF]]
|
||||
[cors]
|
||||
allowed_origin=http://localhost:9876
|
||||
|
||||
[[post-config|\$NOVA_CONF]]
|
||||
[cors]
|
||||
allowed_origin=http://localhost:9876
|
||||
EOL
|
||||
|
||||
# Start devstack.
|
||||
su - stack /devstack/unstack.sh
|
||||
su - stack /devstack/stack.sh
|
|
@ -1,35 +0,0 @@
|
|||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
import cloudsYamlPath from './test/functional/helpers/cloudsYamlPath'
|
||||
|
||||
export default {
|
||||
entry: ['./src/index.js'],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'js-openstack-lib.js',
|
||||
library: 'JSOpenStackLib',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: ['transform-inline-environment-variables']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/helpers\/cloudsConfig/,
|
||||
'json!yaml!' + cloudsYamlPath)
|
||||
],
|
||||
node: {
|
||||
fs: 'empty'
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
- job:
|
||||
name: js-openstack-lib-functional-tests-base
|
||||
# We use tox-functional-consumer as a base job because it's
|
||||
# the closest we've got. It'll cause tox to get installed, but
|
||||
# we override the run so it shouldn't matter.
|
||||
parent: devstack-tox-functional-consumer
|
||||
pre-run:
|
||||
- playbooks/prepare-env-for-tests.yml
|
||||
# NOTE(yoctozepto): devstack starts WSGI-based, API services too early
|
||||
# to make post-config apply to them
|
||||
# see: https://bugs.launchpad.net/devstack/+bug/1860287
|
||||
- playbooks/fixup-devstack.yml
|
||||
run: playbooks/run-npm.yml
|
||||
post-run:
|
||||
- playbooks/fetch-javascript-output.yml
|
||||
vars:
|
||||
npm_command: functional-test
|
||||
|
||||
# NOTE(yoctozepto): we need relaxed CORS allowed_origin to be able to
|
||||
# test browsers without hacking them to ignore CORS
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$KEYSTONE_CONF:
|
||||
cors:
|
||||
allowed_origin: '*'
|
||||
$GLANCE_API_CONF:
|
||||
cors:
|
||||
allowed_origin: '*'
|
||||
$NEUTRON_CONF:
|
||||
cors:
|
||||
allowed_origin: '*'
|
||||
$NOVA_CONF:
|
||||
cors:
|
||||
allowed_origin: '*'
|
||||
|
||||
devstack_services:
|
||||
tls-proxy: false # FIXME(yoctozepto): we can't have tls atm
|
||||
|
||||
# NOTE(yoctozepto): disable Swift & Cinder - not tested atm
|
||||
# but enabled by parent (let's conserve resources)
|
||||
|
||||
# Swift services
|
||||
s-account: false
|
||||
s-container: false
|
||||
s-object: false
|
||||
s-proxy: false
|
||||
|
||||
# Cinder services
|
||||
c-api: false
|
||||
c-bak: false
|
||||
c-sch: false
|
||||
c-vol: false
|
||||
cinder: false
|
||||
|
||||
- job:
|
||||
name: js-openstack-lib-unit-tests-nodejs12
|
||||
parent: nodejs-run-test-browser
|
||||
vars:
|
||||
node_version: 12
|
||||
|
||||
- job:
|
||||
name: js-openstack-lib-functional-tests-nodejs12
|
||||
parent: js-openstack-lib-functional-tests-base
|
||||
vars:
|
||||
node_version: 12
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
- project:
|
||||
templates:
|
||||
- nodejs8-docs
|
||||
- nodejs8-publish-to-npm
|
||||
check:
|
||||
jobs:
|
||||
- nodejs-run-lint
|
||||
- nodejs-run-test-browser
|
||||
- js-openstack-lib-unit-tests-nodejs12
|
||||
gate:
|
||||
jobs:
|
||||
- nodejs-run-lint
|
||||
- nodejs-run-test-browser
|
||||
- js-openstack-lib-unit-tests-nodejs12
|
||||
experimental:
|
||||
jobs:
|
||||
- js-openstack-lib-functional-tests-nodejs12
|
Loading…
Reference in New Issue