Retire Senlin: remove repo content

Senlin project is retiring
- https://review.opendev.org/c/openstack/governance/+/919347

this commit remove the content of this project repo

Depends-On: https://review.opendev.org/c/openstack/project-config/+/919348/
Change-Id: I02c14c754009f34fb36328e5dac73568bea7f672
This commit is contained in:
Ghanshyam Mann 2024-05-10 14:29:03 -07:00
parent dadcdeb7ea
commit 83ccfcbeb3
163 changed files with 8 additions and 12327 deletions

View File

@ -1,6 +0,0 @@
[run]
branch = True
source = senlin_tempest_plugin
[report]
ignore_errors = True

19
.gitignore vendored
View File

@ -1,19 +0,0 @@
__pycache__
.testrepository
*~
*.eggs
*.egg-info
*.iml
*.log
*.pyc
*.swp
*.swo
.coverage
.coverage.*
.idea
.project
.pydevproject
.tox
.venv
.DS_Store
.stestr

View File

@ -1,3 +0,0 @@
[DEFAULT]
test_path=./senlin_tempest_plugin/tests/
top_dir=./

View File

@ -1,52 +0,0 @@
- project:
queue: senlin
templates:
- check-requirements
- tempest-plugin-jobs
check:
jobs:
- senlin-dsvm-tempest-py3-api
- senlin-dsvm-tempest-py3-api-2023-1
- senlin-dsvm-tempest-py3-api-zed
- senlin-dsvm-tempest-py3-api-yoga
- senlin-dsvm-tempest-py3-api-xena
- senlin-dsvm-tempest-py3-api-wallaby
- senlin-tempest-api-ipv6-only
- senlin-dsvm-tempest-py3-functional
- senlin-dsvm-tempest-py3-integration:
voting: false
gate:
jobs:
- senlin-dsvm-tempest-py3-api
- senlin-tempest-api-ipv6-only
- senlin-dsvm-tempest-py3-functional
- job:
name: senlin-dsvm-tempest-py3-api-2023-1
parent: senlin-dsvm-tempest-py3-api
nodeset: openstack-single-node-jammy
override-checkout: stable/2023.1
- job:
name: senlin-dsvm-tempest-py3-api-zed
parent: senlin-dsvm-tempest-py3-api
nodeset: openstack-single-node-focal
override-checkout: stable/zed
- job:
name: senlin-dsvm-tempest-py3-api-yoga
parent: senlin-dsvm-tempest-py3-api
nodeset: openstack-single-node-focal
override-checkout: stable/yoga
- job:
name: senlin-dsvm-tempest-py3-api-xena
parent: senlin-dsvm-tempest-py3-api
nodeset: openstack-single-node-focal
override-checkout: stable/xena
- job:
name: senlin-dsvm-tempest-py3-api-wallaby
parent: senlin-dsvm-tempest-py3-api
nodeset: openstack-single-node-focal
override-checkout: stable/wallaby

View File

@ -1,91 +0,0 @@
Before You Start
================
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
https://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Where to Start
==============
There are many ways to start your contribution.
Sign on a bug to fix
--------------------
Bugs related to senlin are reported and tracked on the individual sites on
Launchpad:
- Senlin Server: https://bugs.launchpad.net/senlin
- Senlin Client: https://bugs.launchpad.net/python-senlinclient
- Senlin Dashboard: https://bugs.launchpad.net/senlin-dashboard
You can pick any bug item that has not been assigned to work on. Each bug fix
patch should be accompanied with a release note.
Pick a TODO item
----------------
Senlin team maintains a ``TODO.rst`` file under the root directory, where you
can add new items, claim existing items and remove items that are completed.
You may want to check if there are items you can pick by:
#. Propose a patch to remove the item from the ``TODO.rst`` file.
#. Add an item to the `etherpad page`_ which the core team uses to track the
progress of individual work items.
#. Start working on the item and keep updating your progress on the `etherpad
page`_, e.g. paste the patch review link to the page.
#. Mark the item from the `etherpad page`_ as completed when the patches are
all merged.
Start a Bigger Effort
---------------------
Senlin team also maintains a ``FEATURES.rst`` file under the root directory,
where you can add new items by proposing a patch to the file or claim an item
to work on. However, the work items in the ``FEATURES.rst`` file are all
non-trivial, thus demands for a deeper discussion before being worked on. The
expected workflow for these items is:
#. Propose a spec file to the ``doc/specs`` directory describing the detailed
design and other options, if any.
#. Work with the reviewers to polish the design until it is accepted.
#. Propose blueprint(s) to track the progress of the work item by registering
them at the `blueprint page`_.
#. Start working on the blueprints and checking in patches. Each patch should
have a ``partial-blueprint: <blueprint>`` tag in its commit message.
#. For each blueprint, add an item to the `etherpad page`_ so that it can be
closely tracked in weekly meetings.
#. Mark the blueprint(s) as completed when all related patches are merged.
#. Propose a patch to the ``FEATURES.rst`` file to remove the work item.
#. Propose a separate release note patch for the new feature.
Reporting Bugs
==============
Bugs should be filed on Launchpad site:
- Senlin Server: https://bugs.launchpad.net/senlin
- Senlin Client: https://bugs.launchpad.net/python-senlinclient
- Senlin Dashboard: https://bugs.launchpad.net/senlin-dashboard
Meet the Developers
===================
Real-time communication among developers are mostly done via IRC.
The team is using the #senlin channel on oftc.net.
.. _`etherpad page`: https://etherpad.openstack.org/p/senlin-newton-workitems
.. _`blueprint page`: https://blueprints.launchpad.net/senlin

View File

@ -1,18 +0,0 @@
Senlin Style Commandments
=========================
- Step 1: Read the OpenStack Style Commandments
https://docs.openstack.org/hacking/latest/
- Step 2: Read on
Senlin Specific Commandments
----------------------------
- [S318] Use assertion ``assertIsNone(A)`` instead of ``assertEqual(A, None)``
or ``assertEqual(None, A)``.
- [S319] Use ``jsonutils`` functions rather than using the ``json`` package
directly.
- [S320] Default arguments of a method should not be mutable.
- [S321] The api_version decorator has to be the first decorator on a method.
- [S322] LOG.warn is deprecated. Enforce use of LOG.warning.
- [S323] Use assertTrue(...) rather than assertEqual(True, ...).

176
LICENSE
View File

@ -1,176 +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,46 +1,10 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/senlin.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
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".
.. Change things from this point on
==============================
Tempest integration of Senlin
==============================
This project contains the Tempest plugin for the Senlin project for
OpenStack Clustering.
For more information about Senlin see:
https://docs.openstack.org/senlin/latest/
For more information about Tempest plugins see:
https://docs.openstack.org/tempest/latest/plugin.html
* Free software: Apache license
* Source: https://opendev.org/openstack/senlin-tempest-plugin
Installing
----------
Clone this repository to the destination machine, and call from the tempest repo::
$ tox -e venv-tempest -- pip install (path to the senlin-tempest-plugin directory)
Running the tests
-----------------
To run all the tests from this plugin, call from the tempest repo::
$ tox -e all -- senlin_tempest_plugin
To run a single test case, call with full path, for example::
$ tox -e all -- senlin_tempest_plugin.tests.api.policies.test_policy_update.TestPolicyUpdate.test_policy_update
To retrieve a list of all tempest tests, run::
$ testr list-tests
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

View File

@ -1,2 +0,0 @@
sphinx!=2.1.0,>=2.0.0 # BSD
reno>=2.5.0 # Apache-2.0

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of senlin-tempest-plugin
to support python 2.7 is OpenStack Train. The minimum version of Python now
supported by senlin-tempest-plugin is Python 3.6.

View File

@ -1,11 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
openstacksdk>=0.24.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
tempest>=17.1.0 # Apache-2.0
tenacity>=4.9.0 # Apache-2.0

View File

@ -1,21 +0,0 @@
====================
Tempest Integration
====================
This directory contains Tempest tests to cover senlin project.
To list all senlin tempest cases, go to tempest directory, then run::
$ testr list-tests senlin
To run only these tests in tempest, go to tempest directory, then run::
$ ./run_tempest.sh -N -- senlin
To run a single test case, go to tempest directory, then run with test case name, e.g.::
$ ./run_tempest.sh -N -- senlin_tempest_plugin.tempest.api.test_cluster_basic.TestClusterBasic.test_cluster_create_delete
If you can't find run_tempest.sh script in tempest directory, that means the script has been removed in a certain version.
Then you can use "nosetests -v" to replace "./run_tempest.sh -N" in above command.
More information about running tempest test can be found here: https://docs.openstack.org/tempest/latest/

View File

@ -1,53 +0,0 @@
#
# 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.
from tempest import config
from tempest import test
CONF = config.CONF
class BaseSenlinTest(test.BaseTestCase):
credentials = ['primary', 'admin']
default_params = config.service_client_config()
# NOTE: Tempest uses timeout values of compute API if project specific
# timeout values don't exist.
default_params_with_timeout_values = {
'build_interval': CONF.compute.build_interval,
'build_timeout': CONF.compute.build_timeout
}
default_params_with_timeout_values.update(default_params)
client = None
max_api_version = '1.0'
@classmethod
def skip_checks(cls):
super(BaseSenlinTest, cls).skip_checks()
if not CONF.service_available.senlin:
skip_msg = 'Senlin is disabled'
raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
super(BaseSenlinTest, cls).setup_clients()
@classmethod
def resource_setup(cls):
super(BaseSenlinTest, cls).resource_setup()
cls.max_api_version = cls.client.get_max_api_version()

View File

@ -1,220 +0,0 @@
#
# 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 time
from urllib import parse as urllib
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from tempest import config as cfg
from tempest.lib.common import rest_client
from tempest.lib import exceptions
CONF = cfg.CONF
class ClusteringAPIClient(rest_client.RestClient):
version = 'v1'
api_microversion = 'latest'
def get_headers(self, accept_type=None, send_type=None):
headers = super(ClusteringAPIClient, self).get_headers(
accept_type=accept_type, send_type=send_type)
headers['openstack-api-version'] = ('clustering %s' %
self.api_microversion)
return headers
def get_resp(self, resp, body):
# Parse status code and location
res = {
'status': int(resp.pop('status')),
'location': resp.pop('location', None)
}
# Parse other keys included in resp
res.update(resp)
# Parse body, Python 3 returns string "b'null'" for delete operations
if (body is not None and
str(body) not in ["", "null", "b'null'", "b''"]):
res['body'] = self._parse_resp(body)
else:
res['body'] = None
return res
def get_obj(self, obj_type, obj_id, params=None):
uri = '{0}/{1}/{2}'.format(self.version, obj_type, obj_id)
if params:
uri += '?{0}'.format(urllib.urlencode(params))
resp, body = self.get(uri)
return self.get_resp(resp, body)
def create_obj(self, obj_type, attrs):
uri = '{0}/{1}'.format(self.version, obj_type)
resp, body = self.post(uri, body=jsonutils.dumps(attrs))
return self.get_resp(resp, body)
def list_objs(self, obj_type, params=None):
uri = '{0}/{1}'.format(self.version, obj_type)
if params:
uri += '?{0}'.format(urllib.urlencode(params))
resp, body = self.get(uri)
return self.get_resp(resp, body)
def update_obj(self, obj_type, obj_id, attrs):
uri = '{0}/{1}/{2}'.format(self.version, obj_type, obj_id)
resp, body = self.patch(uri, body=jsonutils.dumps(attrs))
return self.get_resp(resp, body)
def delete_obj(self, obj_type, obj_id):
uri = '{0}/{1}/{2}'.format(self.version, obj_type, obj_id)
resp, body = self.request('DELETE', uri)
return self.get_resp(resp, body)
def validate_obj(self, obj_type, attrs):
uri = '{0}/{1}/validate'.format(self.version, obj_type)
headers = {'openstack-api-version': 'clustering 1.2'}
resp, body = self.post(uri, body=jsonutils.dumps(attrs),
headers=headers)
return self.get_resp(resp, body)
def trigger_webhook(self, webhook_url, params=None):
if params is not None:
params = jsonutils.dumps(params)
resp, body = self.raw_request(webhook_url, 'POST', body=params)
return self.get_resp(resp, body)
def trigger_action(self, obj_type, obj_id, params=None):
uri = '{0}/{1}/{2}/actions'.format(self.version, obj_type, obj_id)
if params is not None:
params = jsonutils.dumps(params)
resp, body = self.request('POST', uri, body=params)
return self.get_resp(resp, body)
def trigger_operation(self, obj_type, obj_id, params):
uri = '{0}/{1}/{2}/ops'.format(self.version, obj_type, obj_id)
data = jsonutils.dumps(params)
resp, body = self.post(uri, body=data)
return self.get_resp(resp, body)
def cluster_replace_nodes(self, obj_type, obj_id, params=None):
uri = '{0}/{1}/{2}/actions'.format(self.version, obj_type, obj_id)
if params is not None:
params = jsonutils.dumps(params)
headers = self.get_headers()
resp, body = self.post(uri, body=params, headers=headers)
return self.get_resp(resp, body)
def list_cluster_policies(self, cluster_id, params=None):
uri = '{0}/clusters/{1}/policies'.format(self.version, cluster_id)
if params:
uri += '?{0}'.format(urllib.urlencode(params))
resp, body = self.get(uri)
return self.get_resp(resp, body)
def get_cluster_policy(self, cluster_id, policy_id):
uri = '{0}/clusters/{1}/policies/{2}'.format(self.version, cluster_id,
policy_id)
resp, body = self.get(uri)
return self.get_resp(resp, body)
def cluster_collect(self, cluster, path):
uri = '{0}/clusters/{1}/attrs/{2}'.format(self.version, cluster, path)
resp, body = self.get(uri)
return self.get_resp(resp, body)
def list_profile_type_operation(self, profile_type, params=None):
uri = '{0}/profile-types/{1}/ops'.format(self.version, profile_type)
if params:
uri += '?{0}'.format(urllib.urlencode(params))
resp, body = self.get(uri)
return self.get_resp(resp, body)
def wait_for_status(self, obj_type, obj_id, expected_status, timeout=None):
if isinstance(expected_status, list):
expected_status_list = expected_status
else:
expected_status_list = [expected_status]
if timeout is None:
timeout = CONF.clustering.wait_timeout
with timeutils.StopWatch(timeout) as timeout_watch:
while timeout > 0:
time.sleep(5)
res = self.get_obj(obj_type, obj_id)
if res['body']['status'] in expected_status_list:
return res
timeout = timeout_watch.leftover(True)
raise Exception('Timeout waiting for status.')
def wait_for_delete(self, obj_type, obj_id, timeout=None):
if timeout is None:
timeout = CONF.clustering.wait_timeout
with timeutils.StopWatch(timeout) as timeout_watch:
while timeout > 0:
try:
self.get_obj(obj_type, obj_id)
except exceptions.NotFound:
return
time.sleep(5)
timeout = timeout_watch.leftover(True)
raise Exception('Timeout waiting for deletion.')
def get_max_api_version(self):
resp, body = self.get('/')
res = self.get_resp(resp, body)
# Verify resp of API versions list
self.expected_success(300, res['status'])
versions = res['body']
# Only version 1.0 API is now supported
if (len(versions) != 1 or 'id' not in versions[0] or
versions[0]['id'] != '1.0'):
raise Exception(versions)
if 'max_version' not in versions[0]:
raise Exception('max_version is missing')
return versions[0]['max_version']
class ClusteringFunctionalClient(ClusteringAPIClient):
"""This is the tempest client for Senlin functional test"""
pass
class ClusteringIntegrationClient(ClusteringAPIClient):
"""This is the tempest client for Senlin integration test"""
pass

View File

@ -1,80 +0,0 @@
#
# 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.
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from tempest import config
from tempest.lib.common import rest_client
from tempest.lib import exceptions
import time
from urllib import parse as urllib
CONF = config.CONF
class V21ComputeClient(rest_client.RestClient):
def __init__(self, auth_provider, service, region, **kwargs):
super(V21ComputeClient, self).__init__(
auth_provider, service, region, **kwargs)
def get_resp(self, resp, body):
# Parse status code and location
res = {
'status': int(resp.pop('status')),
'location': resp.pop('location', None)
}
# Parse other keys included in resp
res.update(resp)
# Parse body
res['body'] = self._parse_resp(body)
return res
def create_obj(self, obj_type, attrs):
uri = '/{0}'.format(obj_type)
resp, body = self.post(uri, body=jsonutils.dumps(attrs))
return self.get_resp(resp, body)
def delete_obj(self, obj_type, obj_id):
uri = '/{0}/{1}'.format(obj_type, obj_id)
resp, body = self.delete(uri)
return self.get_resp(resp, body)
def run_operation_obj(self, obj_type, obj_id, operation, attrs):
uri = '/{0}/{1}/{2}'.format(obj_type, obj_id, operation)
resp, body = self.post(uri, body=jsonutils.dumps(attrs))
return self.get_resp(resp, body)
def get_obj(self, obj_type, obj_id, params=None):
uri = '/{0}/{1}'.format(obj_type, obj_id)
if params:
uri += '?{0}'.format(urllib.urlencode(params))
resp, body = self.get(uri)
return self.get_resp(resp, body)
def wait_for_status(self, obj_type, obj_id, expected_status, timeout=None):
timeout = timeout or CONF.clustering.wait_timeout
with timeutils.StopWatch(timeout) as timeout_watch:
while not timeout_watch.expired():
time.sleep(5)
res = self.get_obj(obj_type, obj_id)
if res['body']['status'] == expected_status:
return res
raise exceptions.TimeoutException()

View File

@ -1,217 +0,0 @@
# 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.
spec_nova_server = {
"type": "os.nova.server",
"version": "1.0",
"properties": {
"flavor": "1",
"name": "new-server-test",
"image": "cirros-0.4.0-x86_64-disk",
"networks": [
{"network": "private"}
]
}
}
spec_heat_stack = {
"type": "os.heat.stack",
"version": "1.0",
"properties": {
"template": {
"heat_template_version": "2014-10-16",
"parameters": {
"str_length": {
"type": "number",
"default": 64
}
},
"resources": {
"random": {
"type": "OS::Heat::RandomString",
"properties": {
"length": {"get_param": "str_length"}
}
}
},
"outputs": {
"result": {
"value": {"get_attr": ["random", "value"]}
}
}
}
}
}
spec_scaling_policy = {
"type": "senlin.policy.scaling",
"version": "1.0",
"properties": {
"event": "CLUSTER_SCALE_IN",
"adjustment": {
"type": "CHANGE_IN_CAPACITY",
"number": 1,
"min_step": 1,
"best_effort": True
}
}
}
spec_lb_policy = {
"type": "senlin.policy.loadbalance",
"version": "1.1",
"properties": {
"pool": {
"protocol": "HTTP",
"protocol_port": 80,
"subnet": "private-subnet",
"lb_method": "ROUND_ROBIN",
"session_persistence": {
"type": "SOURCE_IP",
"cookie_name": "test-cookie"
}
},
"vip": {
"subnet": "private-subnet",
"connection_limit": 100,
"protocol": "HTTP",
"protocol_port": 80
},
"health_monitor": {
"type": "HTTP",
"delay": "1",
"timeout": 1,
"max_retries": 5,
"admin_state_up": True,
"http_method": "GET",
"url_path": "/index.html",
"expected_codes": "200,201,202"
},
"lb_status_timeout": 300
}
}
spec_batch_policy = {
"type": "senlin.policy.batch",
"version": "1.0",
"properties": {
"min_in_service": 1,
"max_batch_size": 1,
"pause_time": 3
}
}
spec_deletion_policy = {
"type": "senlin.policy.deletion",
"version": "1.1",
"properties": {
"criteria": "OLDEST_FIRST"
}
}
spec_deletion_policy_with_hook = {
"type": "senlin.policy.deletion",
"version": "1.1",
"properties": {
"hooks": {
"type": "zaqar",
"timeout": 300,
"params": {
"queue": "test_queue"
}
},
"criteria": "OLDEST_FIRST"
}
}
spec_health_policy = {
"version": "1.1",
"type": "senlin.policy.health",
"description": "A policy for maintaining node health from a cluster.",
"properties": {
"detection": {
"detection_modes": [
{
"type": "NODE_STATUS_POLLING"
},
{
"type": "NODE_STATUS_POLL_URL",
"options": {
"poll_url_retry_limit": 3,
"poll_url": "http://127.0.0.1:5050",
"poll_url_retry_interval": 2
}
}
],
"node_update_timeout": 10,
"interval": 10
},
"recovery": {
"node_delete_timeout": 90,
"actions": [
{
"name": "RECREATE"
}
],
"node_force_recreate": True
}
}
}
spec_health_policy_duplicate_type = {
"version": "1.1",
"type": "senlin.policy.health",
"properties": {
"detection": {
"detection_modes": [
{
"type": "NODE_STATUS_POLLING"
},
{
"type": "NODE_STATUS_POLLING",
}
],
},
"recovery": {
"actions": [
{
"name": "RECREATE"
}
],
}
}
}
spec_health_policy_invalid_combo = {
"version": "1.1",
"type": "senlin.policy.health",
"properties": {
"detection": {
"detection_modes": [
{
"type": "NODE_STATUS_POLLING"
},
{
"type": "LIFECYCLE_EVENTS",
}
],
},
"recovery": {
"actions": [
{
"name": "RECREATE"
}
],
}
}
}

View File

@ -1,71 +0,0 @@
#
# 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.
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from tempest import config
from tempest.lib.common import rest_client
CONF = config.CONF
class V2MessagingClient(rest_client.RestClient):
def __init__(self, auth_provider, service, region, **kwargs):
super(V2MessagingClient, self).__init__(
auth_provider, service, region, **kwargs)
self.uri_prefix = 'v2'
client_id = uuidutils.generate_uuid()
self.headers = {'Client-ID': client_id}
def get_resp(self, resp, body):
# Parse status code and location
res = {
'status': int(resp.pop('status')),
'location': resp.pop('location', None)
}
# Parse other keys included in resp
res.update(resp)
# Parse body
res['body'] = self._parse_resp(body)
return res
def create_queue(self, queue_name):
uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
resp, body = self.put(uri, '', extra_headers=True,
headers=self.headers)
return self.get_resp(resp, body)
def delete_queue(self, queue_name):
uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
resp, body = self.delete(uri, extra_headers=True, headers=self.headers)
return self.get_resp(resp, body)
def list_messages(self, queue_name):
uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
resp, body = self.get(uri, extra_headers=True, headers=self.headers)
return self.get_resp(resp, body)
def post_messages(self, queue_name, messages):
uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
resp, body = self.post(uri, body=jsonutils.dumps(messages),
extra_headers=True,
headers=self.headers)
return self.get_resp(resp, body)

View File

@ -1,76 +0,0 @@
#
# 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.
from oslo_serialization import jsonutils
from oslo_utils import timeutils
import time
from tempest import config
from tempest.lib.common import rest_client
from tempest.lib import exceptions
CONF = config.CONF
class NetworkClient(rest_client.RestClient):
def __init__(self, auth_provider, service, region, **kwargs):
super(NetworkClient, self).__init__(
auth_provider, service, region, **kwargs)
self.uri_prefix = 'v2.0'
def get_resp(self, resp, body):
# Parse status code and location
res = {
'status': int(resp.pop('status')),
'location': resp.pop('location', None)
}
# Parse other keys included in resp
res.update(resp)
# Parse body
res['body'] = self._parse_resp(body)
return res
def create_obj(self, obj_type, attrs):
uri = '{0}/{1}'.format(self.uri_prefix, obj_type)
resp, body = self.post(uri, body=jsonutils.dumps(attrs))
return self.get_resp(resp, body)
def get_obj(self, obj_type, obj_id):
uri = '{0}/{1}/{2}'.format(self.uri_prefix, obj_type, obj_id)
resp, body = self.get(uri)
return self.get_resp(resp, body)
def delete_obj(self, obj_type, obj_id):
uri = '{0}/{1}/{2}'.format(self.uri_prefix, obj_type, obj_id)
resp, body = self.delete(uri)
return self.get_resp(resp, body)
def wait_for_delete(self, obj_type, obj_id, timeout=None):
timeout = timeout or CONF.clustering.wait_timeout
with timeutils.StopWatch(timeout) as timeout_watch:
while not timeout_watch.expired():
try:
self.get_obj(obj_type, obj_id)
except exceptions.NotFound:
return
time.sleep(5)
raise exceptions.TimeoutException()

View File

@ -1,729 +0,0 @@
# 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 functools
from http import client as http
import http.server as BaseHTTPServer
import os
import signal
import tempfile
import tenacity
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
from senlin_tempest_plugin.common import constants
CONF = config.CONF
def is_version_supported(api_version, version):
"""Check if a version is supported by the API.
:param api_version: Reference endpoint API version.
:param version: Version to check against API version.
:return: boolean if the version is supported.
"""
api_split = list(map(int, api_version.split('.')))
ver_split = list(map(int, version.split('.')))
if api_split[0] > ver_split[0] or (
api_split[0] == ver_split[0] and api_split[1] >= ver_split[1]):
return True
return False
def api_microversion(api_microversion):
"""Decorator used to specify api_microversion for test."""
def decorator(func):
@functools.wraps(func)
def wrapped(self):
if not is_version_supported(self.max_api_version,
api_microversion):
msg = ('This test case uses a client with API version {0}, '
'but Senlin service has max API version {1}.').format(
api_microversion,
self.max_api_version)
raise self.skipException(msg)
old = self.client.api_microversion
self.client.api_microversion = api_microversion
try:
func(self)
finally:
self.client.api_microversion = old
return wrapped
return decorator
def prepare_and_cleanup_for_nova_server(base, cidr, spec=None):
keypair_name = create_a_keypair(base, is_admin_manager=False)
if spec is None:
base.spec = constants.spec_nova_server
else:
base.spec = spec
base.spec['properties']['key_name'] = keypair_name
base.addCleanup(delete_a_keypair, base, keypair_name,
is_admin_manager=False)
n_name = base.spec['properties']['networks'][0]['network']
network_id = create_a_network(base, name=n_name)
base.addCleanup(delete_a_network, base, network_id)
subnet_id = create_a_subnet(base, network_id, cidr)
base.addCleanup(delete_a_subnet, base, subnet_id)
def create_spec_from_config(network_name=None):
"""Utility function that creates a spec object from tempest config"""
spec = constants.spec_nova_server
spec['properties']['flavor'] = CONF.compute.flavor_ref
spec['properties']['image'] = CONF.compute.image_ref
if network_name:
spec['properties']['networks'][0]['network'] = network_name
return spec
def create_a_profile(base, spec=None, name=None, metadata=None):
"""Utility function that generates a Senlin profile."""
if spec is None:
spec = constants.spec_nova_server
if name is None:
name = data_utils.rand_name("tempest-created-profile")
if metadata:
spec['properties']['metadata'] = metadata
params = {
'profile': {
'name': name,
'spec': spec,
}
}
res = base.client.create_obj('profiles', params)
return res['body']['id']
def delete_a_profile(base, profile_id, ignore_missing=False):
"""Utility function that deletes a Senlin profile."""
res = base.client.delete_obj('profiles', profile_id)
if res['status'] == 404:
if ignore_missing:
return
raise exceptions.NotFound()
def create_a_cluster(base, profile_id, desired_capacity=0, min_size=0,
max_size=-1, timeout=None, metadata=None, name=None,
config=None, wait_timeout=None):
"""Utility function that generates a Senlin cluster.
Create a cluster and return it after it is ACTIVE. The function is used for
deduplicate code in API tests where an 'existing' cluster is needed.
"""
if name is None:
name = data_utils.rand_name("tempest-created-cluster")
params = {
'cluster': {
'profile_id': profile_id,
'desired_capacity': desired_capacity,
'min_size': min_size,
'max_size': max_size,
'timeout': timeout,
'metadata': metadata,
'name': name,
'config': config
}
}
res = base.client.create_obj('clusters', params)
cluster_id = res['body']['id']
action_id = res['location'].split('/actions/')[1]
base.client.wait_for_status('actions', action_id, 'SUCCEEDED',
wait_timeout)
return cluster_id
def update_a_cluster(base, cluster_id, profile_id=None, name=None,
expected_status='SUCCEEDED', metadata=None,
timeout=None, wait_timeout=None):
"""Utility function that updates a Senlin cluster.
Update a cluster and return it after it is ACTIVE.
"""
params = {
'cluster': {
'profile_id': profile_id,
'metadata': metadata,
'name': name,
'timeout': timeout
}
}
res = base.client.update_obj('clusters', cluster_id, params)
action_id = res['location'].split('/actions/')[1]
base.client.wait_for_status('actions', action_id, expected_status,
wait_timeout)
return res['body']
def get_a_cluster(base, cluster_id, expected_status=None, wait_timeout=None):
"""Utility function that gets a Senlin cluster."""
if expected_status is None:
res = base.client.get_obj('clusters', cluster_id)
else:
base.client.wait_for_status('clusters', cluster_id, expected_status,
wait_timeout)
res = base.client.get_obj('clusters', cluster_id)
return res['body']
def list_clusters(base):
"""Utility function that lists Senlin clusters."""
res = base.client.list_objs('clusters')
return res['body']
def _return_last_value(retry_state):
return retry_state.outcome.result()
@tenacity.retry(
retry=(tenacity.retry_if_exception_type(exceptions.Conflict) |
tenacity.retry_if_result(lambda x: x is False)),
wait=tenacity.wait_fixed(2),
retry_error_callback=_return_last_value,
stop=tenacity.stop_after_attempt(5)
)
def delete_a_cluster(base, cluster_id, wait_timeout=None):
"""Utility function that deletes a Senlin cluster."""
res = base.client.delete_obj('clusters', cluster_id)
action_id = res['location'].split('/actions/')[1]
action = base.client.wait_for_status(
'actions', action_id, ['SUCCEEDED', 'FAILED'], wait_timeout)
if action['body']['status'] == 'FAILED':
return False
base.client.wait_for_delete("clusters", cluster_id, wait_timeout)
return True
def create_a_node(base, profile_id, cluster_id=None, metadata=None,
role=None, name=None, wait_timeout=None):
"""Utility function that creates a node.
Create a node and return it after it is ACTIVE. This function is for
minimizing the code duplication that could happen in API tests where
an 'existing' Senlin node is needed.
"""
if name is None:
name = data_utils.rand_name("tempest-created-node")
params = {
'node': {
'profile_id': profile_id,
'cluster_id': cluster_id,
'metadata': metadata,
'role': role,
'name': name
}
}
res = base.client.create_obj('nodes', params)
node_id = res['body']['id']
action_id = res['location'].split('/actions/')[1]
base.client.wait_for_status('actions', action_id, 'SUCCEEDED',
wait_timeout)
res = base.client.get_obj('nodes', node_id)
return res['body']['id']
def get_a_node(base, node_id, show_details=False):