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:
ricolin 2023-10-06 12:27:05 +08:00 committed by Ghanshyam
parent 346f7eeda0
commit d2e61be7cd
62 changed files with 11 additions and 5812 deletions

174
LICENSE
View File

@ -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.

View File

@ -1,67 +1,14 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. 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
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
(Optional:)
For an alternative project, please see <alternative project name> at
<alternative project URL>.
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

14
Vagrantfile vendored
View File

@ -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

View File

@ -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]

View File

@ -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
}
})

View File

@ -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."

View File

@ -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}

View File

@ -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.

View File

@ -1,5 +0,0 @@
===============
Getting Started
===============
This section TBD.

View File

@ -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

View File

@ -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

View File

@ -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 wont 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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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?

View File

@ -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
}
})
}

View File

@ -1,2 +0,0 @@
require('@babel/register')
module.exports = require('./karma.conf.babel').default

View File

@ -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"
]
}

View File

@ -1,4 +0,0 @@
---
- hosts: all
roles:
- fetch-javascript-output

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
---
- hosts: all
roles:
- revoke-sudo
- js-package-manager

View File

@ -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)
}
}

View File

@ -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'

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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'
}))
}
}

View File

@ -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])
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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))
})
})
})

View File

@ -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

View File

@ -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'

View File

@ -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
}

View File

@ -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()
})
})
})
})

View File

@ -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))
})
})
})

View File

@ -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))
})
})
})

View File

@ -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))
})
})
})

View File

@ -1,5 +0,0 @@
import Jasmine from 'jasmine'
const jasmine = new Jasmine()
jasmine.loadConfigFile('test/functional/jasmine.json')
jasmine.execute()

View File

@ -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))
})
})
})

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
})
})
})

View File

@ -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))
})
})
})

View File

@ -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))
})
})
})

View File

@ -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
}
})

View File

@ -1,5 +0,0 @@
import Jasmine from 'jasmine'
const jasmine = new Jasmine()
jasmine.loadConfigFile('test/unit/jasmine.json')
jasmine.execute()

View File

@ -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))
})
})
})

View File

@ -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))
})
})

View File

@ -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'
})
})
})

View File

@ -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

View File

@ -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'
}
}

View File

@ -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

View File

@ -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