From e7c69f048c77ae52679c95b3fd5de156fa90d7c7 Mon Sep 17 00:00:00 2001 From: jh629g Date: Mon, 15 Jul 2019 10:24:49 -0500 Subject: [PATCH] Initial Ranger-Tempest-Plugin Commit First commit with zuul tests and migration of existing Ranger-Tempest- Plugin codebase. Change-Id: I45d6946a397cc148ec3789af1c1ad4374a99396a --- .zuul.yaml | 4 + Dockerfile | 100 +++++ changed_python_files.sh | 46 +++ flake8rc | 18 + pylintrc | 378 ++++++++++++++++++ ranger_tempest_plugin/README.rst | 85 ++++ ranger_tempest_plugin/__init__.py | 0 ranger_tempest_plugin/clients.py | 47 +++ ranger_tempest_plugin/config.py | 75 ++++ ranger_tempest_plugin/data_utils.py | 89 +++++ ranger_tempest_plugin/plugin.py | 46 +++ .../ranger_tempest_plugin-blacklist.txt | 6 + ranger_tempest_plugin/schemas/__init__.py | 0 .../schemas/customers_schema.py | 190 +++++++++ .../schemas/flavors_schema.py | 251 ++++++++++++ ranger_tempest_plugin/schemas/group_schema.py | 210 ++++++++++ .../schemas/images_schema.py | 201 ++++++++++ .../schemas/regions_schema.py | 207 ++++++++++ ranger_tempest_plugin/services/__init__.py | 0 ranger_tempest_plugin/services/base_client.py | 169 ++++++++ ranger_tempest_plugin/services/cms_client.py | 129 ++++++ ranger_tempest_plugin/services/fms_client.py | 149 +++++++ ranger_tempest_plugin/services/grp_client.py | 116 ++++++ ranger_tempest_plugin/services/ims_client.py | 102 +++++ ranger_tempest_plugin/services/rms_client.py | 159 ++++++++ ranger_tempest_plugin/tests/__init__.py | 0 ranger_tempest_plugin/tests/api/__init__.py | 0 ranger_tempest_plugin/tests/api/base.py | 52 +++ ranger_tempest_plugin/tests/api/cms_base.py | 324 +++++++++++++++ ranger_tempest_plugin/tests/api/fms_base.py | 261 ++++++++++++ ranger_tempest_plugin/tests/api/ims_base.py | 244 +++++++++++ ranger_tempest_plugin/tests/api/rms_base.py | 30 ++ .../tests/api/test_customers.py | 344 ++++++++++++++++ .../tests/api/test_customers_negative.py | 42 ++ .../tests/api/test_flavors.py | 361 +++++++++++++++++ .../tests/api/test_flavors_negative.py | 44 ++ .../tests/api/test_images.py | 260 ++++++++++++ .../tests/api/test_images_negative.py | 47 +++ .../tests/api/test_region_groups.py | 87 ++++ .../tests/api/test_regiongroups_negative.py | 42 ++ .../tests/api/test_regions.py | 177 ++++++++ .../tests/api/test_regions_negative.py | 42 ++ .../tests/scenario/__init__.py | 0 setup.cfg | 34 ++ setup.py | 29 ++ tempest_setup/.stestr.conf | 6 + tempest_setup/accounts.yaml | 19 + tempest_setup/create_tenant.sh | 23 ++ tempest_setup/ranger-tempest.sh | 104 +++++ tempest_setup/tempest.conf | 118 ++++++ tox.ini | 71 ++++ 51 files changed, 5538 insertions(+) create mode 100644 .zuul.yaml create mode 100644 Dockerfile create mode 100755 changed_python_files.sh create mode 100644 flake8rc create mode 100644 pylintrc create mode 100755 ranger_tempest_plugin/README.rst create mode 100755 ranger_tempest_plugin/__init__.py create mode 100755 ranger_tempest_plugin/clients.py create mode 100755 ranger_tempest_plugin/config.py create mode 100644 ranger_tempest_plugin/data_utils.py create mode 100755 ranger_tempest_plugin/plugin.py create mode 100755 ranger_tempest_plugin/ranger_tempest_plugin-blacklist.txt create mode 100644 ranger_tempest_plugin/schemas/__init__.py create mode 100644 ranger_tempest_plugin/schemas/customers_schema.py create mode 100644 ranger_tempest_plugin/schemas/flavors_schema.py create mode 100644 ranger_tempest_plugin/schemas/group_schema.py create mode 100644 ranger_tempest_plugin/schemas/images_schema.py create mode 100644 ranger_tempest_plugin/schemas/regions_schema.py create mode 100755 ranger_tempest_plugin/services/__init__.py create mode 100755 ranger_tempest_plugin/services/base_client.py create mode 100755 ranger_tempest_plugin/services/cms_client.py create mode 100755 ranger_tempest_plugin/services/fms_client.py create mode 100755 ranger_tempest_plugin/services/grp_client.py create mode 100755 ranger_tempest_plugin/services/ims_client.py create mode 100755 ranger_tempest_plugin/services/rms_client.py create mode 100755 ranger_tempest_plugin/tests/__init__.py create mode 100755 ranger_tempest_plugin/tests/api/__init__.py create mode 100755 ranger_tempest_plugin/tests/api/base.py create mode 100755 ranger_tempest_plugin/tests/api/cms_base.py create mode 100755 ranger_tempest_plugin/tests/api/fms_base.py create mode 100755 ranger_tempest_plugin/tests/api/ims_base.py create mode 100755 ranger_tempest_plugin/tests/api/rms_base.py create mode 100755 ranger_tempest_plugin/tests/api/test_customers.py create mode 100755 ranger_tempest_plugin/tests/api/test_customers_negative.py create mode 100755 ranger_tempest_plugin/tests/api/test_flavors.py create mode 100755 ranger_tempest_plugin/tests/api/test_flavors_negative.py create mode 100755 ranger_tempest_plugin/tests/api/test_images.py create mode 100755 ranger_tempest_plugin/tests/api/test_images_negative.py create mode 100755 ranger_tempest_plugin/tests/api/test_region_groups.py create mode 100755 ranger_tempest_plugin/tests/api/test_regiongroups_negative.py create mode 100755 ranger_tempest_plugin/tests/api/test_regions.py create mode 100755 ranger_tempest_plugin/tests/api/test_regions_negative.py create mode 100755 ranger_tempest_plugin/tests/scenario/__init__.py create mode 100755 setup.cfg create mode 100755 setup.py create mode 100644 tempest_setup/.stestr.conf create mode 100644 tempest_setup/accounts.yaml create mode 100644 tempest_setup/create_tenant.sh create mode 100755 tempest_setup/ranger-tempest.sh create mode 100644 tempest_setup/tempest.conf create mode 100644 tox.ini diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 0000000..51be8cc --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,4 @@ +- project: + name: x/ranger-tempest-plugin + templates: + - openstack-python-jobs diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bcb6f47 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,100 @@ +FROM ubuntu:16.04 + +ENV DEBIAN_FRONTEND noninteractive +ENV container docker +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 + +RUN apt -qq update && \ + apt -y install git \ + netcat \ + netbase \ + openssh-server \ + python-minimal \ + python-setuptools \ + python-pip \ + python-dev \ + python-dateutil \ + ca-certificates \ + openstack-pkg-tools \ + apache2 \ + libmysqlclient-dev \ + gcc \ + g++ \ + libffi-dev \ + libssl-dev --no-install-recommends && \ + apt-get clean && \ + rm -rf \ + /var/lib/apt/lists/* \ + /tmp/* \ + /var/tmp/* \ + /usr/share/man \ + /usr/share/doc \ + /usr/share/doc-base + +RUN pip install -U setuptools && \ + pip install wheel && \ + pip install --upgrade six && \ + pip install pbr==2.0.0 + + +########################################################################## +### aic-orm-tempest-plugin setup +########################################################################## +WORKDIR / +COPY . ranger-tempest-plugin/ + +WORKDIR /ranger-tempest-plugin/ +RUN python setup.py develop + +########################################################################## +### END OF ranger-tempest-plugin setup +########################################################################## + +########################################################################## +### openstack tempest setup steps +########################################################################## + +### reset workdir to root before executing tempest steps +WORKDIR / + +### git clone tempest +RUN git clone https://git.openstack.org/openstack/tempest + +### now run 'pip install -r requirements' +RUN pip install -r /tempest/requirements.txt && \ + pip install -r /tempest/test-requirements.txt + +### create required tempest directories - and remove .stestr folder +RUN mkdir -p /tempest/logs \ + && mkdir -p /tempest/tempest_lock \ + && mkdir -p /tempest/images \ + && mkdir -p /var/log/tempest \ + && rm -rf /tempest/.stestr \ + && rm -rf /tempest/.stestr.conf + +# copy tempest test setup files +COPY tempest_setup/.stestr.conf /tempest/ +COPY tempest_setup/create_tenant.sh /tempest/etc +COPY tempest_setup/accounts.yaml /tempest/etc +COPY tempest_setup/tempest.conf /tempest/etc + +########################################################################## +### END OF openstack tempest setup steps +########################################################################## + +########################################################################## +### RUN tempest tests on test_regions +########################################################################## +### create egg-info for tempest +WORKDIR /tempest/ +RUN python /tempest/setup.py develop +#ENTRYPOINT ostestr run ranger_tempest_plugin.tests.api.test_regions \ +# && /bin/bash +#ENTRYPOINT ostestr run ranger_tempest_plugin.tests.api.test_flavors/ \ +# && /bin/bash +#ENTRYPOINT ostestr run ranger_tempest_plugin.tests.api.test_customers/ \ +# && /bin/bash +#ENTRYPOINT ostestr run ranger_tempest_plugin.tests.api.test_images/ \ +# && /bin/bash + diff --git a/changed_python_files.sh b/changed_python_files.sh new file mode 100755 index 0000000..a3aa216 --- /dev/null +++ b/changed_python_files.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e +export TERM=xterm-color + +# ========================================================================= +# This script will find all the changed python files and run +# 1. flake8 +# 3. pylint +# for each of the changed file and provide the relevant output +# ========================================================================= + +TOXINIDIR=$1 +CHECKTYPE=$2 +POSTARGS=$3 +PYLINTRCFILE=${TOXINIDIR}/pylintrc +FLAKERC=${TOXINIDIR}/flake8rc + +# Find all the changed python files that needs to be checked / linted for +# ==================================================================== +# If dev is passed as {postargs} then it is indicative that the +# check will be performed in a local dev environment +# Else it is indicative of a pipeline for container build process +# ==================================================================== + +if [[ "${POSTARGS}" = "dev" ]]; then + CHANGEDFILES=($(git status -s | grep -v 'D' | egrep '\.py' | awk -F ' ' '{print $2}' || true)) + LENGTHARR=${#CHANGEDFILES[@]} +else + CHANGEDFILES=($(git diff-tree --no-commit-id --name-only --diff-filter AM -r HEAD | egrep '\.py' || true)) + LENGTHARR=${#CHANGEDFILES[@]} +fi + + +for ((i=0;i<$LENGTHARR; i++));do + echo "$(tput setaf 4)============================================================$(tput setaf 9)" + echo "$(tput setaf 5) Performing check on ${CHANGEDFILES[i]}$(tput setaf 9)" + echo "$(tput setaf 4)============================================================$(tput setaf 9)" + echo ${CHANGEDFILES[i]} + if [[ "${CHECKTYPE}" = "flake" ]]; then + flake8 --config=$FLAKERC ${CHANGEDFILES[i]} + elif [[ "${CHECKTYPE}" = "pylint" ]]; then + pylint -rn --rcfile=$PYLINTRCFILE ${CHANGEDFILES[i]} + fi +done + diff --git a/flake8rc b/flake8rc new file mode 100644 index 0000000..09ba5f9 --- /dev/null +++ b/flake8rc @@ -0,0 +1,18 @@ +[flake8] +ignore = E125,E123,E129 +show-source = False +exclude = + .git, + .venv, + .tox, + dist, + doc, + *egg, + *.pyc, + *.egg-info, + .cache, + .eggs +enable-extensions = H106,H203,H904 +import-order-style = google +filename = *.py + diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..bbe5e55 --- /dev/null +++ b/pylintrc @@ -0,0 +1,378 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" + +disable=protected-access,fixme,too-many-branches, + attribute-defined-outside-init,too-many-locals, + too-many-arguments,too-many-statements, + too-many-return-statements,too-few-public-methods, + import-error,too-many-lines,too-many-instance-attributes, + too-many-public-methods,duplicate-code,broad-except, + redefined-builtin,missing-docstring,no-member,bad-continuation, + bad-indentation,superfluous-parens,literal-comparison,unused-import, + no-self-use,unused-variable,anomalous-backslash-in-string, + line-too-long,len-as-condition,unnecessary-pass, + # Crashes, see #743. + redefined-variable-type, + # bug in 1.7.2 https://github.com/PyCQA/pylint/issues/1493 + not-callable + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=parseable + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,75}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# List of decorators that define properties, such as abc.abstractproperty. +property-classes=abc.abstractproperty + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject, optparse.Values, thread._local, _thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + +# List of decorators that create context managers from functions, such as +# contextlib.contextmanager. +contextmanager-decorators=contextlib.contextmanager + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception + diff --git a/ranger_tempest_plugin/README.rst b/ranger_tempest_plugin/README.rst new file mode 100755 index 0000000..2b9f1b1 --- /dev/null +++ b/ranger_tempest_plugin/README.rst @@ -0,0 +1,85 @@ +================================= +Tempest Integration of ORM +================================= + +This directory contains Tempest tests to cover the ORM project, as well +as a plugin to automatically load these tests into tempest. + +See the tempest plugin docs for information on using it: +https://docs.openstack.org/tempest/latest/#using-plugins + +See the tempest docs for information on writing new tests etc: +https://docs.openstack.org/tempest/latest/ + +Quickstart +---------- + +#. You first need to install Tempest in a venv. If virtual environment is not + installed install it using "sudo apt-get install python-virtualenv":: + + $ virtualenv .venv + $ source .venv/bin/activate + $ cd tempest + $ pip install tox + $ pip install tempest + +#. Clone/install the plugin:: + + $ pip install -e + For example: + pip install -e /opt/stack/ranger + + + +tempest.conf file +-------------------- + +This file should be present in tempest/etc + + +Following content must be added in tempest.conf file: + + +[auth] +# Use predefined credentials instead of creating users on the fly. +use_dynamic_credentials=false +admin_username = username +admin_password = password +admin_project_name= tenant/project/customer name +test_accounts_file=/opt/stack/tempest/etc/accounts.yaml -- Provide the accurate path of the accounts.yaml file + +[oslo_concurrency] +lock_path = Provide respective path for oslo_concurrency + +[orm] +uri = Provide orm url. For exmple: http://orm.***.***.***.com +catalog_type = orm + +accounts.yaml file +------------------ + +accounts.yaml file must be added in the path tempest/etc + +Following content must be present with the given format in accounts.yaml file: + +- username: 'username1' + tenant_name: 'tenant_name1' + password: 'password1' +- username: 'username2' + tenant_name: 'tenant_name2' + password: 'password2' + + + +Running the tests +----------------- + +To run all tests from this plugin, run from the tempest repo:: + + $ tox -e all-plugin -- ranger + +To run all tempest tests including this plugin, run:: + + $ tox -e all-plugin + + diff --git a/ranger_tempest_plugin/__init__.py b/ranger_tempest_plugin/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ranger_tempest_plugin/clients.py b/ranger_tempest_plugin/clients.py new file mode 100755 index 0000000..2901fc6 --- /dev/null +++ b/ranger_tempest_plugin/clients.py @@ -0,0 +1,47 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.services import base_client +from ranger_tempest_plugin.services.cms_client import CmsClient +from ranger_tempest_plugin.services.fms_client import FmsClient +from ranger_tempest_plugin.services.ims_client import ImsClient +from ranger_tempest_plugin.services.rms_client import RmsClient + +from tempest import clients +from tempest import config + +CONF = config.CONF + + +class OrmClientManager(clients.Manager): + + def __init__(self, credential=None): + super(OrmClientManager, self).__init__(credential) + self.cms_client = CmsClient(base_client.RangerAuthProvider(credential), + CONF.identity.catalog_type, + CONF.identity.region, + CONF.ranger.RANGER_CMS_BASE_URL) + self.fms_client = FmsClient(base_client.RangerAuthProvider(credential), + CONF.identity.catalog_type, + CONF.identity.region, + CONF.ranger.RANGER_FMS_BASE_URL) + self.rms_client = RmsClient(base_client.RangerAuthProvider(credential), + CONF.identity.catalog_type, + CONF.identity.region, + CONF.ranger.RANGER_RMS_BASE_URL) + self.ims_client = ImsClient(base_client.RangerAuthProvider(credential), + CONF.identity.catalog_type, + CONF.identity.region, + CONF.ranger.RANGER_IMS_BASE_URL) diff --git a/ranger_tempest_plugin/config.py b/ranger_tempest_plugin/config.py new file mode 100755 index 0000000..16a09f9 --- /dev/null +++ b/ranger_tempest_plugin/config.py @@ -0,0 +1,75 @@ +# Copyright 2015 +# All Rights Reserved. +# +# 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_config import cfg + + +service_available_group = cfg.OptGroup( + name="service_available", + title="Available OpenStack Services" +) + +ServiceAvailableGroup = [ + cfg.BoolOpt("ranger", default=False, + help="Whether or not ranger is expected to be available") +] + +orm_group = cfg.OptGroup( + name="ranger", + title="Ranger Service option" +) + +OrmGroup = [ + cfg.StrOpt("uri", + default="orm", + help="Uri of the orm service."), + cfg.StrOpt("cms_port", + default='7080', + help="cms port of the orm url."), + cfg.StrOpt("fms_port", + default='8082', + help="fms port of the orm url."), + cfg.StrOpt("region_port", + default='8080', + help="region port of the orm url."), + cfg.BoolOpt("alt_region_available", + default=None, + help="alt_region_available of the orm alternate region."), + cfg.StrOpt("ims_port", + default='8084', + help="ims port of the orm url."), + cfg.StrOpt("image_url", + help="swift container url where image is located"), + cfg.StrOpt("RANGER_CMS_BASE_URL", + help="Ranger Project Service URL"), + cfg.StrOpt("RANGER_FMS_BASE_URL", + help="Ranger Flavor Service URL"), + cfg.StrOpt("RANGER_IMS_BASE_URL", + help="Ranger Image Service URL"), + cfg.StrOpt("RANGER_RMS_BASE_URL", + help="Ranger Region Service URL"), + cfg.BoolOpt('verify', + default=False, + help='Flag for SSL verfiy Enabled/Disabled.'), + cfg.BoolOpt('auth_enabled', + default=False, + help='Token Authentication enabled/disabled'), + cfg.ListOpt("flavor_series", + default=['xx'], + help="Supported flavor series"), + cfg.StrOpt("domain", + default='Default', + help="Domain used for Ranger tempest testing") +] diff --git a/ranger_tempest_plugin/data_utils.py b/ranger_tempest_plugin/data_utils.py new file mode 100644 index 0000000..7c22a45 --- /dev/null +++ b/ranger_tempest_plugin/data_utils.py @@ -0,0 +1,89 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 random + +from oslo_log import log as logging +from tempest import config +from tempest.lib.common.utils import data_utils + +LOG = logging.getLogger(__name__) +CONF = config.CONF +identity_url = CONF.identity.uri_v3.strip('/v3') + + +def rand_region_status(exclude=[]): + statuses = {'functional', 'maintenance', 'down', 'building'}.difference( + exclude) + return random.choice(list(statuses)) + + +def rand_region_metadata(): + metadata = {} + for i in range(random.randint(2, 10)): + metadata[data_utils.rand_name()] = [data_utils.arbitrary_string()] + return metadata + + +def rand_region(id=None): + if id is None: + id = data_utils.rand_name() + region_dict = { + 'status': rand_region_status(), + 'id': id, + 'name': id, + 'designType': data_utils.arbitrary_string(), + 'locationType': data_utils.arbitrary_string(), + 'vlcpName': data_utils.arbitrary_string(), + 'description': data_utils.arbitrary_string(), + 'rangerAgentVersion': data_utils.arbitrary_string(), + 'OSVersion': data_utils.arbitrary_string(), + 'CLLI': data_utils.arbitrary_string(), + 'address': { + 'country': data_utils.arbitrary_string(), + 'state': data_utils.arbitrary_string(), + 'city': data_utils.arbitrary_string(), + 'street': data_utils.arbitrary_string(), + 'zip': str(data_utils.rand_int_id(start=10000, end=99999)) + }, + 'metadata': { + data_utils.rand_name(): [data_utils.arbitrary_string()], + data_utils.rand_name(): [data_utils.arbitrary_string()] + }, + 'endpoints': [{ + 'publicURL': data_utils.rand_url(), + 'type': 'dashboard' + }, { + 'publicURL': identity_url, + 'type': 'identity' + }, { + 'publicURL': data_utils.rand_url(), + 'type': 'ord' + }] + } + return region_dict + + +def rand_region_group(region_ids, id=None): + if id is None: + id = data_utils.rand_name() + group_dict = { + 'name': id, + 'id': id, + 'description': data_utils.arbitrary_string(), + 'regions': region_ids + } + + return group_dict diff --git a/ranger_tempest_plugin/plugin.py b/ranger_tempest_plugin/plugin.py new file mode 100755 index 0000000..1983101 --- /dev/null +++ b/ranger_tempest_plugin/plugin.py @@ -0,0 +1,46 @@ +# Copyright 2015 +# All Rights Reserved. +# +# 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 os + +from ranger_tempest_plugin import config as project_config + +from tempest import config +from tempest.test_discover import plugins + + +class RangerPlugin(plugins.TempestPlugin): + def get_opt_lists(self): + return [( + project_config.orm_group.name, + project_config.OrmGroup)] + + def load_tests(self): + base_path = os.path.split(os.path.dirname( + os.path.abspath(__file__)))[0] + test_dir = "ranger_tempest_plugin/tests" + full_test_dir = os.path.join(base_path, test_dir) + return full_test_dir, base_path + + def register_opts(self, conf): + config.register_opt_group( + conf, + project_config.service_available_group, + project_config.ServiceAvailableGroup) + config.register_opt_group( + conf, + project_config.orm_group, + project_config.OrmGroup) diff --git a/ranger_tempest_plugin/ranger_tempest_plugin-blacklist.txt b/ranger_tempest_plugin/ranger_tempest_plugin-blacklist.txt new file mode 100755 index 0000000..f10624f --- /dev/null +++ b/ranger_tempest_plugin/ranger_tempest_plugin-blacklist.txt @@ -0,0 +1,6 @@ +############################################################################################### +# Blacklist ORM Tests since the tests are having issues. +# IST is looking into the same +# This will be removed once IST resolves the same [no ETA has been provided] +############################################################################################### +(?:aic_orm_tempest_plugin.*) \ No newline at end of file diff --git a/ranger_tempest_plugin/schemas/__init__.py b/ranger_tempest_plugin/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ranger_tempest_plugin/schemas/customers_schema.py b/ranger_tempest_plugin/schemas/customers_schema.py new file mode 100644 index 0000000..925ac99 --- /dev/null +++ b/ranger_tempest_plugin/schemas/customers_schema.py @@ -0,0 +1,190 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_status = { + 'type': 'string', + 'enum': ['Success', 'no regions', 'Error', 'Pending', 'Submitted'] +} + +_links = { + 'type': 'object', + 'properties': { + 'self': {'type': 'string'} + }, + 'required': ['self'] +} + +_region = { + 'type': 'object', + 'properties': { + 'added': {'type': 'string'}, + 'id': {'type': 'string'}, + 'links': _links + }, + 'required': ['added', 'id', 'links'] +} + +_user = { + 'type': 'object', + 'properties': { + 'added': {'type': 'string', 'format': 'date-time'}, + 'id': {'type': 'string'}, + 'links': _links + }, + 'required': ['added', 'id', 'links'] +} + +_customer = { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'links': _links, + 'created': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['id', 'links', 'created'] +} + +create_customer = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'customer': _customer, + 'transaction_id': {'type': 'string'} + }, + 'required': ['customer', 'transaction_id'] + } +} + +update_customer = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'customer': _customer, + 'transaction_id': {'type': 'string'} + }, + 'required': ['customer', 'transaction_id'] + } +} + +add_regions = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'regions': { + 'type': 'array', + 'items': _region + }, + 'transaction_id': {'type': 'string'} + }, + 'required': ['regions', 'transaction_id'] + } +} + +add_users = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'users': { + 'type': 'array', + 'items': _user + }, + 'transaction_id': {'type': 'string'} + }, + 'required': ['users', 'transaction_id'] + } +} + +replace_users = add_users + +add_metadata = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'customer': _customer, + 'transaction_id': {'type': 'string'} + }, + 'required': ['customer', 'transaction_id'] + } +} + +replace_metadata = add_metadata + +enable_customer = add_metadata + +get_customer = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'status': _status, + 'uuid': {'type': 'string'}, + 'users': {'type': 'array'}, + 'description': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'defaultQuotas': {'type': 'array'}, + 'name': {'type': 'string'}, + 'regions': {'type': 'array'}, + 'custId': {'type': 'string'}, + 'metadata': {'type': 'object'} + }, + 'required': ['status', 'uuid', 'users', 'description', 'enabled', + 'defaultQuotas', 'name', 'regions', 'custId', 'metadata'] + } +} + +list_customer = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'customers': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'status': _status, + 'description': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'num_regions': {'type': 'integer'}, + 'regions': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'required': ['status', 'description', 'enabled', + 'num_regions', 'regions', 'id', 'name'] + } + } + }, + 'required': ['customers'] + } +} + +delete_customer = { + 'status_code': [204] +} + +delete_region_from_customer = delete_customer + +delete_default_user = delete_customer + +delete_user_from_region = delete_customer diff --git a/ranger_tempest_plugin/schemas/flavors_schema.py b/ranger_tempest_plugin/schemas/flavors_schema.py new file mode 100644 index 0000000..e8d0ceb --- /dev/null +++ b/ranger_tempest_plugin/schemas/flavors_schema.py @@ -0,0 +1,251 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_regions = { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": {"type": "string"}, + "type": {"type": "string"}, + "name": {"type": "string"}, + "error_message": {"type": "string"} + } + }, + "required": ["status", "type", "name"] +} + +_extra_specs = { + "type": "object", + "additionalProperties": { + "type": "string" + } +} + +_tags = { + "type": "object", + "items": { + "type": "array", + "items": {"type": "string"} + } +} + +_tenants = { + "type": "array", + "items": { + "type": "string" + } +} + +_flavor = { + "type": "object", + "properties": { + "flavor": { + "type": "object", + "properties": { + "status": {"type": "string"}, + "alias": {"type": "string"}, + "description": {"type": "string"}, + "tags": _tags, + "series": {"type": "string"}, + "extra-specs": _extra_specs, + "ram": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "ephemeral": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "visibility": { + "type": "string", + "enum": ["public", "private"] + }, + "options": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "regions": _regions, + "vcpus": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "swap": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "disk": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "tenants": _tenants, + "id": {"type": "string"}, + "name": {"type": "string"} + }, + "required": ["status", "series", "extra-specs", + "ram", "ephemeral", "visibility", "vcpus", + "regions", "swap", "disk", "tenants", + "id", "name"] + } + }, + "required": ["flavor"] +} + +create_flavor = { + "status_code": [201], + "response_body": _flavor +} + +get_flavor = { + "status_code": [200], + "response_body": _flavor +} + +delete_flavor = { + "status_code": [204] +} + +list_flavors = { + "status_code": [200], + "response_body": { + "type": "object", + "properties": { + "flavors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": {"type": "string"}, + "description": {"type": "string"}, + "tags": _tags, + "series": {"type": "string"}, + "extra-specs": _extra_specs, + "ram": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "ephemeral": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "visibiity": { + "type": "string", + "enum": ["public", "private"] + }, + "options": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "regions": _regions, + "vcpus": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "swap": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "disk": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "tenants": _tenants, + "id": {"type": "string"}, + "name": {"type": "string"} + }, + "required": ["status", "description", "series", + "extra-specs", "ram", "ephemeral", + "visibility", "vcpus", "regions", "swap", + "disk", "tenants", "id", "name"] + } + } + }, + "required": ["flavors"] + } +} + +get_extra_specs = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'os_extra_specs': _extra_specs + } + } +} + +add_extra_specs = { + "status_code": [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'os_extra_specs': _extra_specs + } + } +} + +update_extra_specs = get_extra_specs + +delete_extra_specs = { + "status_code": [204] +} + +add_tags = { + 'status_code': [201], + 'response_body': _tags +} + +delete_tags = { + "status_code": [204] +} + +get_tags = { + 'status_code': [200], + 'response_body': _tags +} + +update_tags = get_tags + +add_region = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'regions': _regions + } + } +} + +delete_region = { + "status_code": [204] +} + +add_tenant = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'tenants': _tenants + } + } +} + +delete_tenant = { + "status_code": [204] +} diff --git a/ranger_tempest_plugin/schemas/group_schema.py b/ranger_tempest_plugin/schemas/group_schema.py new file mode 100644 index 0000000..5ae1363 --- /dev/null +++ b/ranger_tempest_plugin/schemas/group_schema.py @@ -0,0 +1,210 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_delete = { + 'status_code': [204] +} + +_status = { + 'type': 'string', + 'enum': ['Success', 'no regions', 'Error', 'Pending', 'Submitted'] +} + +_links = { + 'type': 'object', + 'properties': { + 'self': {'type': 'string'} + }, + 'required': ['self'] +} + +_region = { + 'type': 'object', + 'properties': { + 'added': {'type': 'string'}, + 'id': {'type': 'string'}, + 'links': _links + }, + 'required': ['added', 'id', 'links'] +} + +_group = { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'links': _links, + 'created': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['id', 'links', 'created'] +} + +_users = { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'domain': {'type': 'string'}, + }, + 'required': ['id', 'domain'] +} + +create_group = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'group': _group, + 'transaction_id': {'type': 'string'} + }, + 'required': ['group', 'transaction_id'] + } +} + +get_group = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'status': _status, + 'uuid': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'domain': {'type': 'string'}, + 'name': {'type': 'string'}, + 'regions': {'type': 'array'}, + 'description': {'type': 'string'}, + 'users': {'type': 'array'} + }, + 'required': ['status', 'uuid', 'enabled', 'domain', 'name', + 'regions', 'description'] + } +} + +list_groups = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'groups': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'status': _status, + 'description': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'domain': {'type': 'string'}, + 'regions': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'required': ['status', 'description', 'enabled', + 'domain', 'regions', 'id', 'name'] + } + } + }, + 'required': ['groups'] + } +} + +delete_group = _delete +delete_groups_region = _delete + + +_roles = { + 'type': 'object', + 'properties': { + 'roles': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'customer': {'type': 'string'}, + 'domain': {'type': 'string'} + }, + 'required': ['roles'] +} + +assign_group_roles = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'transaction_id': {'type': 'string'}, + 'roles': { + 'type': 'array', + 'items': _roles + }, + 'links': _links, + 'created': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['transaction_id', 'roles', 'links', 'created'] + } +} + +unassign_group_role = _delete + +list_group_roles = { + 'status_code': [200], + 'response_body': { + 'type': 'array', + 'properties': { + 'roles': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'customer': {'type': 'string'}, + 'domain': {'type': 'string'} + }, + 'required': ['roles'] + } +} + +add_groups_region = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'regions': { + 'type': 'array', + 'items': _region + }, + 'transaction_id': {'type': 'string'} + }, + 'required': ['regions', 'transaction_id'] + } +} + +add_groups_users = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'transaction_id': {'type': 'string'}, + 'users': { + 'type': 'array', + 'items': _users + }, + 'links': _links, + 'created': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['transaction_id', 'users', 'links', 'created'] + } +} + +delete_groups_user = _delete diff --git a/ranger_tempest_plugin/schemas/images_schema.py b/ranger_tempest_plugin/schemas/images_schema.py new file mode 100644 index 0000000..600cde8 --- /dev/null +++ b/ranger_tempest_plugin/schemas/images_schema.py @@ -0,0 +1,201 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_region_status = { + "type": "string", + "enum": ["Submitted", "Pending", "Success", "Error"] +} + +_regions = { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": _region_status, + "name": {"type": "string"}, + "checksum": {"type": "string"}, + "size": {"type": "string"}, + "virtual_size": {"type": "string"}, + "type": {"type": "string"}, + "error_message": {"type": "string"} + } + } +} + +_tags = { + "type": "array", + "items": { + "type": "string" + } +} + +_status = { + "type": "string", + "enum": ["Success", "no regions", "Error", "Pending"] +} + +_links = { + "type": "object", + "properties": { + "self": {"type": "string"} + }, + "required": ["self"] +} + +_image = { + "type": "object", + "properties": { + "image": { + "type": "object", + "properties": { + "enabled": {"type": "boolean"}, + "links": { + "type": "object", + "items": { + "self": {"type": "string"} + } + }, + "locations": { + "type": "array", + "items": { + "type": "string" + } + }, + "file": {"type": "string"}, + "owner": {"type": "string"}, + "id": {"type": "string"}, + "self": {"type": "string"}, + "created-at": {"type": "string"}, + "updated-at": {"type": "string"}, + "regions": _regions, + "disk-format": {"type": "string"}, + "min-ram": { + "type": "number" + }, + "schema": {"type": "string"}, + "status": _status, + "customers": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": _tags, + "visibility": { + "type": "string", + "enum": ["public", "private"] + }, + "min-disk": { + "type": "number" + }, + "properties": { + "type": "object", + "items": { + "type": "array", + "items": {"type": "string"} + } + }, + "name": {"type": "string"}, + "url": {"type": "string"}, + "protected": { + "type": "boolean" + }, + "container-format": {"type": "string"} + }, + "required": ["links", "enabled", "locations", "file", + "status", "owner", "id", "self", "disk-format", + "min-ram", "properties", "visibility", + "regions", "schema", "min-disk", "customers", + "protected", "url", "name", "container-format"] + } + }, + "required": ["image"] +} + +create_image = { + "status_code": [201], + "response_body": _image +} + +add_tenant_to_image = { + "status_code": [201], + "response_body": _image +} + +get_image = { + "status_code": [200], + "response_body": _image +} + +update_tenant = get_image + +update_image = get_image + +delete_image = { + "status_code": [204] +} + +delete_tenant_from_image = { + "status_code": [204] +} + +add_region = { + "status_code": [200, 201], + 'response_body': { + 'type': 'object', + 'properties': { + 'regions': _regions + } + } +} + +delete_region = delete_image + +_region_names_array = { + "type": "array", + "items": { + "type": "string" + } +} + +list_images = { + "status_code": [200], + "response_body": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": _status, + "regions": _region_names_array, + "name": {"type": "string"}, + "visibiity": { + "type": "string", + "enum": ["public", "private"] + }, + "id": {"type": "string"} + }, + "required": ["status", "regions", "name", + "visibility", "id"] + } + } + }, + "required": ["images"] + } +} + +enable_image_resp = get_image diff --git a/ranger_tempest_plugin/schemas/regions_schema.py b/ranger_tempest_plugin/schemas/regions_schema.py new file mode 100644 index 0000000..3bcf5e4 --- /dev/null +++ b/ranger_tempest_plugin/schemas/regions_schema.py @@ -0,0 +1,207 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_metadata = { + 'type': 'object', + 'items': { + 'type': 'array', + 'items': {'type': 'string'} + } +} + +_region = { + 'type': 'object', + 'properties': { + 'status': {'type': 'string'}, + 'endpoints': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'publicURL': {'type': 'string'}, + 'type': {'type': 'string'} + }, + 'required': ['publicURL', 'type'] + } + }, + 'CLLI': {'type': 'string'}, + 'name': {'type': 'string'}, + 'description': {'type': 'string'}, + 'designType': {'type': 'string'}, + 'locationType': {'type': 'string'}, + 'vlcpName': {'type': 'string'}, + 'address': { + 'type': 'object', + 'properties': { + 'country': {'type': 'string'}, + 'state': {'type': 'string'}, + 'street': {'type': 'string'}, + 'zip': {'type': 'string'}, + 'city': {'type': 'string'}, + }, + 'required': ['country', 'state', 'street', 'zip', 'city'] + }, + 'rangerAgentVersion': {'type': 'string'}, + 'OSVersion': {'type': 'string'}, + 'id': {'type': 'string'}, + 'metadata': _metadata, + 'created': {'type': 'string', 'format': 'date-time'}, + 'modified': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['status', 'endpoints', 'rangerAgentVersion', 'OSVersion', + 'CLLI', 'created', 'modified', 'metadata', 'address', + 'locationType', 'designType', 'description', 'name', 'id', + 'vlcpName'] +} + +get_region = { + 'status_code': [200], + 'response_body': _region +} + +get_region_metadata = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': {'metadata': _metadata}, + 'required': ['metadata'] + } +} + +create_region = { + 'status_code': [201], + 'response_body': _region +} + +update_region = create_region + +update_metadata = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': {'metadata': _metadata}, + 'required': ['metadata'] + } +} + +update_status = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'status': {'type': 'string'}, + 'links': { + 'type': 'object', + 'properties': { + 'self': {'type': 'string'} + } + } + }, + 'required': ['status', 'links'] + } +} + +delete_region = { + 'status_code': [204] +} + +list_region = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'regions': { + 'type': 'array', + 'items': _region + } + }, + 'required': ['regions'] + } +} + +list_region_v1 = { + 'status_code': [200], + 'response_body': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'status': {'type': 'string'}, + 'vLCP_name': {'type': 'string'}, + 'ORD_EP': {'type': 'string'}, + 'horizon_EP': {'type': 'string'}, + 'design_type': {'type': 'string'}, + 'rangerAgentVersion': {'type': 'string'}, + 'id': {'type': 'string'}, + 'OS_version': {'type': 'string'}, + 'keystone_EP': {'type': 'string'}, + 'zone_name': {'type': 'string'}, + 'location_type': {'type': 'string'} + } + } + } +} + +_region_group = { + 'type': 'object', + 'properties': { + 'description': {'type': 'string'}, + 'links': { + 'type': 'object', + 'properties': {'self': {'type': 'string'}} + }, + 'created': {'type': 'string', 'format': 'date-time'}, + 'modified': {'type': 'string', 'format': 'date-time'}, + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'required': ['description', 'created', 'modified', 'id', 'name'] +} + +create_region_group = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'group': _region_group + }, + 'required': ['group'] + } +} + +update_region_group = create_region_group + +get_region_group = { + 'status_code': [200], + 'response_body': _region_group +} + +list_region_groups = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'groups': { + 'type': 'array', + 'items': _region_group + } + }, + 'required': ['groups'] + } +} + +delete_region_group = { + 'status_code': [204] +} diff --git a/ranger_tempest_plugin/services/__init__.py b/ranger_tempest_plugin/services/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ranger_tempest_plugin/services/base_client.py b/ranger_tempest_plugin/services/base_client.py new file mode 100755 index 0000000..7612d0d --- /dev/null +++ b/ranger_tempest_plugin/services/base_client.py @@ -0,0 +1,169 @@ +# Copyright 2015 +# All Rights Reserved. +# +# 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 json + +import requests + +from tempest import config +from tempest.lib import auth +from tempest.lib.common import rest_client + +CONF = config.CONF + + +class ResponseError(Exception): + pass + + +class ConnectionError(Exception): + pass + + +class RangerClientBase(rest_client.RestClient): + + rms_url = CONF.ranger.RANGER_RMS_BASE_URL + auth_region = CONF.identity.region + timeout = 10 + + # def get_keystone_ep(rms_url, region_name): + def get_keystone_ep(self): + """Get the Keystone EP from tempest conf. + """ + return CONF.identity.uri_v3.strip('/v3') + + def get_token(self, timeout, host): + headers = { + 'Content-Type': 'application/json', + } + url = '%s/v3/auth/tokens' + data = ''' +{ + "auth":{ + "identity":{ + "methods":[ + "password" + ], + "password":{ + "user":{ + "domain":{ + "name":"%s" + }, + "name":"%s", + "password":"%s" + } + } + }, + "scope":{ + "project":{ + "name":"%s", + "domain":{ + "name":"%s" + } + } + } + } +}''' + if not CONF.ranger.auth_enabled: + return None + + region = self.auth_region + + keystone_ep = self.get_keystone_ep() + if keystone_ep is None: + raise ConnectionError( + 'Failed in get_token, host: {}, region: {}'.format(host, + region)) + + url = url % (keystone_ep,) + data = data % (CONF.auth.admin_domain_name, + CONF.auth.admin_username, + CONF.auth.admin_password, + CONF.auth.admin_project_name, + CONF.auth.admin_domain_name,) + + try: + resp = requests.post(url, + timeout=timeout, + data=data, + headers=headers) + if resp.status_code != 201: + raise ResponseError( + 'Failed to get token (Reason: {})'.format( + resp.status_code)) + return resp.headers['x-subject-token'] + + except Exception as e: + raise ConnectionError(e.message) + + def get_headers(self): + headers = {'X-Auth-Region': CONF.identity.region, + 'X-Auth-Token': self.get_token(self.timeout, self.rms_url), + 'X-RANGER-Tracking-Id': 'test', + 'X-RANGER-Requester': CONF.auth.admin_username, + 'X-RANGER-Client': 'cli', + 'Content-Type': 'application/json' + } + return headers + + def get_request(self, uri, expected_body_schema): + ex_headers = self.get_headers() + resp, body = self.get(uri, extra_headers=ex_headers) + self.expected_success(200, resp.status) + body = json.loads(body) + self.validate_response(expected_body_schema, resp, body) + return resp, body + + def put_request(self, uri, put_body, expected_body_schema): + ex_headers = self.get_headers() + resp, body = self.put(uri, body=put_body, extra_headers=ex_headers) + self.expected_success([200, 201], resp.status) + body = json.loads(body) + self.validate_response(expected_body_schema, resp, body) + return resp, body + + def delete_request(self, uri, expected_body_schema): + ex_headers = self.get_headers() + resp, body = self.delete(uri, extra_headers=ex_headers) + self.expected_success(204, resp.status) + self.validate_response(expected_body_schema, resp, body) + return resp, body + + def post_request(self, uri, post_body, expected_body_schema): + ex_headers = self.get_headers() + resp, body = self.post(uri, body=post_body, + extra_headers=ex_headers) + self.expected_success([200, 201], resp.status) + body = json.loads(body) + self.validate_response(expected_body_schema, resp, body) + return resp, body + + +class RangerAuthProvider(auth.KeystoneV3AuthProvider): + + def __init__(self, credentials, auth_url=CONF.identity.uri_v3): + super(RangerAuthProvider, self).__init__(credentials, auth_url) + + def auth_request(self, method, url, headers=None, body=None, filters=None): + filters = {'service': 'identity'} + auth_headers = super(RangerAuthProvider, + self).auth_request(method, + url, + filters=filters) + + base_headers = auth_headers[1] + base_headers.update(headers) + auth_req = dict(url=url, headers=base_headers, body=body) + return auth_req['url'], auth_req['headers'], auth_req['body'] diff --git a/ranger_tempest_plugin/services/cms_client.py b/ranger_tempest_plugin/services/cms_client.py new file mode 100755 index 0000000..0fafc02 --- /dev/null +++ b/ranger_tempest_plugin/services/cms_client.py @@ -0,0 +1,129 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 json +import urllib + +from ranger_tempest_plugin.schemas import customers_schema as schema +from ranger_tempest_plugin.services import base_client + +from tempest import config + +CONF = config.CONF + + +class CmsClient(base_client.RangerClientBase): + + cms_url = CONF.ranger.RANGER_CMS_BASE_URL + version = 'v1' + + # POST + + def create_customer(self, **kwargs): + uri = '%s/%s/orm/customers' % (self.cms_url, self.version) + post_body = json.dumps(kwargs) + return self.post_request(uri, post_body, schema.create_customer) + + def add_default_user(self, customer_id, *args): + uri = '%s/%s/orm/customers/%s/users' \ + % (self.cms_url, self.version, customer_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.add_users) + + def add_regions(self, customer_id, regions): + uri = '%s/%s/orm/customers/%s/regions' \ + % (self.cms_url, self.version, customer_id) + post_body = json.dumps(regions) + return self.post_request(uri, post_body, schema.add_regions) + + def add_region_user(self, customer_id, region_id, *args): + uri = '%s/%s/orm/customers/%s/regions/%s/users' \ + % (self.cms_url, self.version, customer_id, region_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.add_users) + + def add_metadata(self, customer_id, metadata): + uri = '%s/%s/orm/customers/%s/metadata' \ + % (self.cms_url, self.version, customer_id) + post_body = json.dumps(metadata) + return self.post_request(uri, post_body, schema.add_metadata) + + # PUT + + def update_customer(self, customer_id, customer): + uri = '%s/%s/orm/customers/%s' \ + % (self.cms_url, self.version, customer_id) + put_body = json.dumps(customer) + return self.put_request(uri, put_body, schema.update_customer) + + def enable_customer(self, customer_id, value): + uri = '%s/%s/orm/customers/%s/enabled' \ + % (self.cms_url, self.version, customer_id) + put_body = json.dumps({'enabled': value}) + return self.put_request(uri, put_body, schema.enable_customer) + + def replace_default_user(self, customer_id, *args): + uri = '%s/%s/orm/customers/%s/users' \ + % (self.cms_url, self.version, customer_id) + put_body = json.dumps(args) + return self.put_request(uri, put_body, schema.replace_users) + + def replace_region_user(self, customer_id, region_id, *args): + uri = '%s/%s/orm/customers/%s/regions/%s/users' \ + % (self.cms_url, self.version, customer_id, region_id) + + put_body = json.dumps(args) + return self.put_request(uri, put_body, schema.replace_users) + + def replace_metadata(self, customer_id, metadata): + uri = '%s/%s/orm/customers/%s/metadata' \ + % (self.cms_url, self.version, customer_id) + put_body = json.dumps(metadata) + return self.put_request(uri, put_body, schema.replace_metadata) + + # GET + + def get_customer(self, identifier): + uri = '%s/%s/orm/customers/%s' \ + % (self.cms_url, self.version, identifier) + return self.get_request(uri, schema.get_customer) + + def list_customers(self, filter=None): + uri = '%s/%s/orm/customers' % (self.cms_url, self.version) + if filter is not None: + uri += '?' + urllib.urlencode(filter) + return self.get_request(uri, schema.list_customer) + + # DELETE + + def delete_region_from_customer(self, customer_id, region_id): + uri = '%s/%s/orm/customers/%s/regions/%s' % ( + self.cms_url, self.version, customer_id, region_id) + return self.delete_request(uri, schema.delete_region_from_customer) + + def delete_customer(self, customer_id): + uri = '%s/%s/orm/customers/%s' \ + % (self.cms_url, self.version, customer_id) + return self.delete_request(uri, schema.delete_customer) + + def delete_default_user(self, customer_id, user_id): + uri = '%s/%s/orm/customers/%s/users/%s' \ + % (self.cms_url, self.version, customer_id, user_id) + return self.delete_request(uri, schema.delete_default_user) + + def delete_region_user(self, customer_id, region_id, user_id): + uri = '%s/%s/orm/customers/%s/regions/%s/users/%s' \ + % (self.cms_url, self.version, customer_id, region_id, user_id) + return self.delete_request(uri, schema.delete_user_from_region) diff --git a/ranger_tempest_plugin/services/fms_client.py b/ranger_tempest_plugin/services/fms_client.py new file mode 100755 index 0000000..cb6afc7 --- /dev/null +++ b/ranger_tempest_plugin/services/fms_client.py @@ -0,0 +1,149 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 json + +from ranger_tempest_plugin.schemas import flavors_schema as schema +from ranger_tempest_plugin.services import base_client + +from tempest import config +from tempest.lib.common import rest_client + +CONF = config.CONF + + +class FmsClient(base_client.RangerClientBase): + + fms_url = CONF.ranger.RANGER_FMS_BASE_URL + version = "v1" + + def create_flavor(self, **kwargs): + uri = '%s/%s/orm/flavors' % (self.fms_url, self.version) + post_body = {"flavor": kwargs} + post_body = json.dumps(post_body) + return self.post_request(uri, post_body, schema.create_flavor) + + def get_flavor(self, identifier, para=None): + if para is None: + uri = '%s/%s/orm/flavors/%s' % (self.fms_url, self.version, + identifier) + else: + uri = '%s/%s/orm/flavors/%s/%s' % (self.fms_url, self.version, + identifier, para) + return self.get_request(uri, schema.get_flavor) + + def list_flavors(self, para=None): + if para is None: + uri = '%s/%s/orm/flavors' % (self.fms_url, self.version) + else: + uri = '%s/%s/orm/flavors/%s' % (self.fms_url, self.version, para) + return self.get_request(uri, schema.list_flavors) + + def delete_region_from_flavor(self, flavor_id, region_id): + uri = '%s/%s/orm/flavors/%s/regions/%s' % (self.fms_url, + self.version, flavor_id, + region_id) + ex_headers = self.get_headers() + resp, body = self.delete(uri, extra_headers=ex_headers) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) + + def delete_flavor(self, flavor_id): + uri = '%s/%s/orm/flavors/%s' %\ + (self.fms_url, self.version, flavor_id) + return self.delete_request(uri, schema.delete_flavor) + + def delete_tags(self, flavor_id, para): + if para is None: + uri = '%s/%s/orm/flavors/%s/tags' % (self.fms_url, self.version, + flavor_id) + else: + uri = '%s/%s/orm/flavors/%s/tags/%s' % (self.fms_url, self.version, + flavor_id, para) + return self.delete_request(uri, schema.delete_tags) + + def get_tags(self, flavor_id): + uri = '%s/%s/orm/flavors/%s/tags' % (self.fms_url, self.version, + flavor_id) + return self.get_request(uri, schema.get_tags) + + def add_tags(self, flavor_id, tag_body): + uri = '%s/%s/orm/flavors/%s/tags' % (self.fms_url, self.version, + flavor_id) + post_body = json.dumps(tag_body) + return self.post_request(uri, post_body, schema.add_tags) + + def update_tags(self, flavor_id, tag_body): + uri = '%s/%s/orm/flavors/%s/tags' % (self.fms_url, self.version, + flavor_id) + put_body = json.dumps(tag_body) + return self.put_request(uri, put_body, schema.update_tags) + + def get_extra_specs(self, flavor_id): + uri = '%s/%s/orm/flavors/%s/os_extra_specs' % (self.fms_url, + self.version, + flavor_id) + return self.get_request(uri, schema.get_extra_specs) + + def add_flvr_tenants(self, flavor_id, tenant_body): + uri = '%s/%s/orm/flavors/%s/tenants/' % (self.fms_url, + self.version, + flavor_id) + post_body = json.dumps(tenant_body) + return self.post_request(uri, post_body, schema.add_tenant) + + def add_flvr_regions(self, flavor_id, region_body): + uri = '%s/%s/orm/flavors/%s/regions' % (self.fms_url, + self.version, + flavor_id) + post_body = json.dumps(region_body) + return self.post_request(uri, post_body, schema.add_region) + + def delete_flvr_region(self, flavor_id, region_id): + uri = '%s/%s/orm/flavors/%s/regions/%s' % (self.fms_url, + self.version, + flavor_id, region_id) + return self.delete_request(uri, schema.delete_region) + + def add_extra_specs(self, flavor_id, extra_specs_body): + uri = '%s/%s/orm/flavors/%s/os_extra_specs' % (self.fms_url, + self.version, + flavor_id) + post_body = json.dumps(extra_specs_body) + return self.post_request(uri, post_body, schema.add_extra_specs) + + def update_extra_specs(self, flavor_id, extra_specs_body): + uri = '%s/%s/orm/flavors/%s/os_extra_specs' % (self.fms_url, + self.version, + flavor_id) + put_body = json.dumps(extra_specs_body) + return self.put_request(uri, put_body, schema.update_extra_specs) + + def delete_extra_specs(self, flavor_id, para): + if para is None: + uri = '%s/%s/orm/flavors/%s/os_extra_specs' % (self.fms_url, + self.version, + flavor_id) + else: + uri = '%s/%s/orm/flavors/%s/os_extra_specs/%s' % (self.fms_url, + self.version, + flavor_id, para) + return self.delete_request(uri, schema.delete_extra_specs) + + def delete_flvr_tenant(self, flavor_id, tenant): + uri = '%s/%s/orm/flavors/%s/tenants/%s' % (self.fms_url, + self.version, + flavor_id, tenant) + return self.delete_request(uri, schema.delete_tenant) diff --git a/ranger_tempest_plugin/services/grp_client.py b/ranger_tempest_plugin/services/grp_client.py new file mode 100755 index 0000000..7ec74ec --- /dev/null +++ b/ranger_tempest_plugin/services/grp_client.py @@ -0,0 +1,116 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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. +# +# Group processes currently are non-functional, but this code is being +# kept for the time being until we are certain it is no longer needed. + +import json +import urllib + +from ranger_tempest_plugin.schemas import group_schema as schema +from ranger_tempest_plugin.services import base_client + +from tempest import config + +CONF = config.CONF + + +class GrpClient(base_client.RangerClientBase): + + cms_url = CONF.ranger.RANGER_CMS_BASE_URL + version = 'v1' + + def create_group(self, **kwargs): + uri = '%s/%s/orm/groups' % (self.cms_url, self.version) + post_body = json.dumps(kwargs) + return self.post_request(uri, post_body, schema.create_group) + + def get_group(self, identifier): + uri = '%s/%s/orm/groups/%s' \ + % (self.cms_url, self.version, identifier) + return self.get_request(uri, schema.get_group) + + def list_groups(self, filter=None): + uri = '%s/%s/orm/groups' % (self.cms_url, self.version) + if filter is not None: + uri += '?' + urllib.urlencode(filter) + return self.get_request(uri, schema.list_groups) + + def add_groups_region(self, group_id, *args): + uri = '%s/%s/orm/groups/%s/regions' % ( + self.cms_url, self.version, group_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.add_groups_region) + + def delete_groups_region(self, group_id, region_id): + uri = '%s/%s/orm/groups/%s/regions/%s' % ( + self.cms_url, self.version, group_id, region_id) + return self.delete_request(uri, schema.delete_groups_region) + + def delete_group(self, group_id): + uri = '%s/%s/orm/groups/%s' \ + % (self.cms_url, self.version, group_id) + return self.delete_request(uri, schema.delete_group) + + def assign_group_roles(self, group_id, *args): + uri = '%s/%s/orm/groups/%s/roles' % ( + self.cms_url, self.version, group_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.assign_group_roles) + + def assign_group_region_roles(self, group_id, region_id, *args): + uri = '%s/%s/orm/groups/%s/regions/%s/roles' % ( + self.cms_url, self.version, group_id, region_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.assign_group_roles) + + def unassign_group_role( + self, group_id, role, assignmenet_type, assignment_value): + uri = '%s/%s/orm/groups/%s/roles/%s/%s/%s' % (self.cms_url, + self.version, + group_id, + role, + assignmenet_type, + assignment_value) + return self.delete_request(uri, schema.unassign_group_role) + + def list_group_roles(self, group_id, params): + uri = '%s/%s/orm/groups/%s/roles/%s' % ( + self.cms_url, self.version, group_id, params) + return self.get_request(uri, schema.list_group_roles) + + def add_group_default_user(self, group_id, *args): + uri = '%s/%s/orm/groups/%s/users' \ + % (self.cms_url, self.version, group_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.add_groups_users) + + def delete_group_default_user(self, group_id, user_id, user_domain): + uri = '%s/%s/orm/groups/%s/users/%s/%s' % ( + self.cms_url, self.version, group_id, user_id, user_domain) + return self.delete_request(uri, schema.delete_groups_region) + + def add_group_region_user(self, group_id, region_id, *args): + uri = '%s/%s/orm/groups/%s/regions/%s/users' \ + % (self.cms_url, self.version, group_id, region_id) + post_body = json.dumps(args) + return self.post_request(uri, post_body, schema.add_groups_users) + + def delete_groups_region_user(self, group_id, region_id, + user_id, user_domain): + uri = '%s/%s/orm/groups/%s/regions/%s/users/%s/%s' % ( + self.cms_url, self.version, group_id, region_id, + user_id, user_domain) + return self.delete_request(uri, schema.delete_groups_region) diff --git a/ranger_tempest_plugin/services/ims_client.py b/ranger_tempest_plugin/services/ims_client.py new file mode 100755 index 0000000..f560f43 --- /dev/null +++ b/ranger_tempest_plugin/services/ims_client.py @@ -0,0 +1,102 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 json + +from ranger_tempest_plugin.schemas import images_schema as schema +from ranger_tempest_plugin.services import base_client +from tempest import config + +CONF = config.CONF + + +class ImsClient(base_client.RangerClientBase): + + ims_url = CONF.ranger.RANGER_IMS_BASE_URL + version = "v1" + + def create_image(self, **kwargs): + uri = '%s/%s/orm/images' % (self.ims_url, self.version) + post_body = {"image": kwargs} + post_body = json.dumps(post_body) + return self.post_request(uri, post_body, schema.create_image) + + def update_image(self, image_id, para=None, **kwargs): + if para is None: + uri = '%s/%s/orm/images/%s' % ( + self.ims_url, self.version, image_id) + else: + uri = '%s/%s/orm/images/%s/%s' % ( + self.ims_url, self.version, image_id, para) + put_body = {"image": kwargs} + put_body = json.dumps(put_body) + return self.put_request(uri, put_body, schema.update_image) + + def get_image(self, identifier, para=None): + if para is None: + uri = '%s/%s/orm/images/%s' % (self.ims_url, self.version, + identifier) + else: + uri = '%s/%s/orm/images/%s/%s' % (self.ims_url, self.version, + identifier, para) + return self.get_request(uri, schema.get_image) + + def list_images(self, para=None): + if para is None: + uri = '%s/%s/orm/images' % (self.ims_url, self.version) + else: + uri = '%s/%s/orm/images/%s' % (self.ims_url, self.version, para) + return self.get_request(uri, schema.list_images) + + def enabled_image(self, image_id, bool): + uri = '%s/%s/orm/images/%s/enabled' \ + % (self.ims_url, self.version, image_id) + put_body = json.dumps({'enabled': bool}) + return self.put_request(uri, put_body, schema.enable_image_resp) + + def add_region_to_image(self, image_id, region_id): + uri = '%s/%s/orm/images/%s/regions/' % (self.ims_url, + self.version, image_id) + post_body = json.dumps({"regions": [{"name": region_id}]}) + return self.post_request(uri, post_body, schema.add_region) + + def delete_region_from_image(self, image_id, region_id): + uri = '%s/%s/orm/images/%s/regions/%s' % (self.ims_url, + self.version, image_id, + region_id) + return self.delete_request(uri, schema.delete_region) + + def delete_image(self, image_id): + uri = '%s/%s/orm/images/%s' % (self.ims_url, self.version, image_id) + return self.delete_request(uri, schema.delete_image) + + def add_customer_to_image(self, image_id, tenant_id): + uri = '%s/%s/orm/images/%s/customers' % ( + self.ims_url, self.version, image_id) + post_body = json.dumps({"customers": [tenant_id]}) + return self.post_request(uri, post_body, schema.add_tenant_to_image) + + def update_customer(self, image_id, tenant_id): + uri = '%s/%s/orm/images/%s/customers' % (self.ims_url, self.version, + image_id) + put_body = json.dumps({"customers": [tenant_id]}) + return self.put_request(uri, put_body, schema.update_tenant) + + def delete_customer_from_image(self, image_id, tenant_id): + uri = '%s/%s/orm/images/%s/customers/%s' % (self.ims_url, + self.version, + image_id, + tenant_id) + return self.delete_request(uri, schema.delete_tenant_from_image) diff --git a/ranger_tempest_plugin/services/rms_client.py b/ranger_tempest_plugin/services/rms_client.py new file mode 100755 index 0000000..1179263 --- /dev/null +++ b/ranger_tempest_plugin/services/rms_client.py @@ -0,0 +1,159 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 json +import urllib + +from ranger_tempest_plugin.schemas import regions_schema as schema +from ranger_tempest_plugin.services import base_client + +from tempest import config + + +CONF = config.CONF + + +class RmsClient(base_client.RangerClientBase): + + rms_url = CONF.ranger.RANGER_RMS_BASE_URL + identity_url = CONF.identity.uri_v3.strip('/v3') + version = "v2" + + def create_region(self, region_id, **kwargs): + uri = '%s/%s/orm/regions' % (self.rms_url, self.version) + post_body = { + 'status': 'functional', + 'name': region_id, + 'id': region_id, + 'description': region_id, + 'designType': 'compact', + 'locationType': 'testlocation', + 'vlcpName': 'testvlcp', + 'address': { + 'country': 'usa', + 'state': 'tx', + 'city': 'austin', + 'street': '12 main', + 'zip': '12345' + }, + 'metadata': { + 'key': ["value"] + }, + 'endpoints': [ + { + 'publicURL': + 'https://dashboard-ranger.%s.com' % region_id, + 'type': 'dashboard' + }, + { + 'publicURL': self.identity_url, + 'type': 'identity' + }, + { + 'publicURL': + 'https://ranger-agent.%s.com:9010' % region_id, + 'type': 'ord' + }, + + ], + 'rangerAgentVersion': '3.6', + 'OSVersion': 'kilo', + 'CLLI': 'testclli' + } + if kwargs is not None: + for key in kwargs: + post_body[key] = kwargs[key] + + post_body = json.dumps(post_body) + return self.post_request(uri, post_body, schema.create_region) + + def update_region(self, region_id, **kwargs): + uri = '%s/%s/orm/regions/%s' % (self.rms_url, self.version, region_id) + put_body = json.dumps(kwargs) + return self.put_request(uri, put_body, schema.update_region) + + def update_region_status(self, region_id, status): + uri = '%s/%s/orm/regions/%s/status' \ + % (self.rms_url, self.version, region_id) + put_body = json.dumps(status) + return self.put_request(uri, put_body, schema.update_status) + + def update_region_metadata(self, region_id, metadata): + uri = '%s/%s/orm/regions/%s/metadata' \ + % (self.rms_url, self.version, region_id) + put_body = json.dumps(metadata) + return self.put_request(uri, put_body, schema.update_metadata) + + def get_region(self, identifier): + uri = '%s/%s/orm/regions/%s' % (self.rms_url, self.version, identifier) + return self.get_request(uri, schema.get_region) + + def get_region_metadata(self, identifier): + uri = '%s/%s/orm/regions/%s/metadata'\ + % (self.rms_url, self.version, identifier) + return self.get_request(uri, schema.get_region_metadata) + + def list_regions_v1(self): + uri = self.rms_url + '/lcp' + return self.get_request(uri, schema.list_region_v1) + + def list_regions(self, filter=None): + uri = '%s/%s/orm/regions' % (self.rms_url, self.version) + if filter is not None: + uri += '?' + urllib.urlencode(filter) + return self.get_request(uri, schema.list_region) + + def delete_region(self, region_id): + uri = '%s/%s/orm/regions/%s' % (self.rms_url, self.version, region_id) + return self.delete_request(uri, schema.delete_region) + + def add_region_metadata(self, region_id, **kwargs): + uri = '%s/%s/orm/regions/%s/metadata'\ + % (self.rms_url, self.version, region_id) + post_body = json.dumps(kwargs) + return self.post_request(uri, post_body, schema.update_metadata) +# +# def delete_region_metadata(self, region_id, key): +# uri = '%s/%s/orm/regions/%s/metadata/%s' % ( +# self.rms_url, self.version, region_id, key) +# ex_headers = self.get_headers() +# resp, body = self.delete(uri, extra_headers=ex_headers) +# self.expected_success(200, resp.status) +# body = json.loads(body) +# return rest_client.ResponseBody(resp, body) + + def create_region_group(self, **kwargs): + uri = '%s/%s/orm/groups' % (self.rms_url, self.version) + post_body = json.dumps(kwargs) + return self.post_request(uri, post_body, schema.create_region_group) + + def update_region_group(self, group_id, **kwargs): + uri = '%s/%s/orm/groups/%s' % (self.rms_url, self.version, group_id) + put_body = json.dumps(kwargs) + return self.put_request(uri, put_body, schema.update_region_group) + + def get_region_group(self, identifier): + uri = '%s/%s/orm/groups/%s'\ + % (self.rms_url, self.version, identifier) + return self.get_request(uri, schema.get_region_group) + + def list_region_groups(self): + uri = '%s/%s/orm/groups' % (self.rms_url, self.version) + return self.get_request(uri, schema.list_region_groups) + + def delete_region_group(self, region_group_id): + uri = '%s/%s/orm/groups/%s' % (self.rms_url, self.version, + region_group_id) + return self.delete_request(uri, schema.delete_region_group) diff --git a/ranger_tempest_plugin/tests/__init__.py b/ranger_tempest_plugin/tests/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ranger_tempest_plugin/tests/api/__init__.py b/ranger_tempest_plugin/tests/api/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ranger_tempest_plugin/tests/api/base.py b/ranger_tempest_plugin/tests/api/base.py new file mode 100755 index 0000000..b74fb9c --- /dev/null +++ b/ranger_tempest_plugin/tests/api/base.py @@ -0,0 +1,52 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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_log import log as logging +from ranger_tempest_plugin import clients + +import six +from tempest import config +from tempest import test + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class BaseOrmTest(test.BaseTestCase): + + credentials = ['admin', 'primary', 'alt'] + + client_manager = clients.OrmClientManager + + build_timeout = 120 + build_interval = 10 + + @classmethod + def setup_clients(cls): + super(BaseOrmTest, cls).setup_clients() + cls.identity_client = cls.os_admin.projects_client + + @classmethod + def skip_checks(cls): + super(BaseOrmTest, cls).skip_checks() + if not CONF.service_available.ranger: + skip_msg = ("%s skipped as ranger is not available" % cls.__name__) + raise cls.skipException(skip_msg) + + def assertExpected(self, expected, actual, excluded_keys): + for key, value in six.iteritems(expected): + if key not in excluded_keys: + self.assertIn(key, actual) + self.assertEqual(value, actual[key], key) diff --git a/ranger_tempest_plugin/tests/api/cms_base.py b/ranger_tempest_plugin/tests/api/cms_base.py new file mode 100755 index 0000000..7e06799 --- /dev/null +++ b/ranger_tempest_plugin/tests/api/cms_base.py @@ -0,0 +1,324 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 random +import time + +from oslo_log import log as logging +from ranger_tempest_plugin.tests.api import base + +from tempest import config + +from tempest.common.utils import data_utils +from tempest.lib import exceptions + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class CmsBaseOrmTest(base.BaseOrmTest): + credentials = ['admin', 'primary', 'alt'] + + @classmethod + def setup_clients(cls): + super(CmsBaseOrmTest, cls).setup_clients() + cls.client = cls.os_primary.cms_client + + @classmethod + def _get_quota(cls): + compute, storage, network, quota = {}, {}, {}, {} + compute["instances"] = "10" + compute["injected-files"] = "10" + compute["key-pairs"] = "10" + compute["ram"] = "10" + compute["vcpus"] = "51" + compute["metadata-items"] = "34" + compute["injected-file-content-bytes"] = "25" + storage["gigabytes"] = "10" + storage["snapshots"] = "10" + storage["volumes"] = "10" + network["floating-ips"] = "10" + network["networks"] = "10" + network["ports"] = "10" + network["routers"] = "10" + network["subnets"] = "10" + network["security-group-rules"] = "51" + network["security-groups"] = "50" + quota['compute'] = [compute] + quota['storage'] = [storage] + quota['network'] = [network] + return quota + + @classmethod + def _get_additional_quota_for_cust(cls): + quota = cls._get_quota() + quota["compute"][0]["floating-ips"] = "10" + quota["compute"][0]["fixed-ips"] = "10" + quota["compute"][0]["injected-file-path-bytes"] = "34" + quota["compute"][0]["server-groups"] = "10" + quota["compute"][0]["server-group-members"] = "34" + quota["network"][0]["health-monitor"] = "10" + quota["network"][0]["member"] = "10" + quota["network"][0]["nat-instance"] = "10" + quota["network"][0]["pool"] = "10" + quota["network"][0]["route-table"] = "10" + quota["network"][0]["vip"] = "10" + return quota + + @classmethod + def _get_customer_params(cls, quota=None, enabled=True, region_users=True, + default_users=True): + region, user, metadata, payload = {}, {}, {}, {} + cust_name = data_utils.rand_name('ormTempestCms') + if not quota: + quota = cls._get_quota() + region['name'] = CONF.identity.region + region['type'] = 'single' + region['quotas'] = [quota] + user['id'] = cls.os_primary.credentials.username + user['role'] = ["admin"] + region["users"] = [user] if region_users else [] + regions = [region] + metadata['my_server_name'] = cust_name + metadata['ocx_cust'] = random.randint(0, 999999999) + payload["description"] = cust_name + payload["enabled"] = True if enabled else False + payload["name"] = cust_name + payload['metadata'] = metadata + payload["regions"] = regions + payload["defaultQuotas"] = [quota] + payload['users'] = [user] if default_users else [] + return payload + + @classmethod + def _get_bare_customer_params(cls): + customer = {} + customer['description'] = '' + customer['enabled'] = True + customer['name'] = data_utils.rand_name('ormTempestCms') + customer['regions'] = [] + customer['defaultQuotas'] = [] + customer['users'] = [] + return customer + + @classmethod + def _get_user_params(cls, alt=False): + users = [] + if not alt: + users.append({'id': cls.os_primary.credentials.username, + 'role': ['admin']}) + else: + users.append({'id': cls.os_alt.credentials.username, + 'role': ['admin_viewer', 'admin_support']}) + return users + + @classmethod + def _get_region_params(cls): + quota = cls._get_quota() + region = {} + region['name'] = CONF.identity.region + region['type'] = 'single' + region['quotas'] = [quota] + return [region] + + @classmethod + def _create_cust_validate_creation_on_dcp_and_lcp(self, **kwargs): + """Creates a customer record + + kwargs contains field data needed for customer POST body: + - name + - description + - enabled + - metadata + - regions + - defaultQuotas + - ephemeral + - regions + - visibility + - tenants + """ + _, body = self.client.create_customer(**kwargs) + customer_id = body["customer"]["id"] + _, customer = self.client.get_customer(customer_id) + if customer["name"] == kwargs["name"]: + if customer["regions"] == []: + customer_status = "no regions" + else: + customer_status = "Success" + + self._wait_for_status(customer_id, customer_status) + return customer_id + else: + message = "customer %s not created successfully" % kwargs["name"] + exceptions.TempestException(message) + + @classmethod + def _wait_for_status(cls, customer_id, status): + customer_status = cls.client.get_customer(customer_id)[1]["status"] + start = int(time.time()) + while customer_status != status: + time.sleep(cls.build_interval) + customer_status = cls.client.get_customer(customer_id)[1]["status"] + if customer_status == 'Error': + message = ('customer %s failed to reach %s status' + ' and is in ERROR status on orm' % + (customer_id, status)) + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ('customer %s failed to reach %s' + 'status within the required time (%s s)' + 'on orm and is in %s status.' + % (customer_id, status, + cls.build_timeout, + customer_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_cust_quota_on_lcp(cls, quota, cust_id): + expected_quota_count = len(quota["compute"][0]) +\ + len(quota["storage"][0]) +\ + len(quota["network"][0]) + actual_quota_count = 0 + body = cls.nova_quotas_client.show_quota_set(cust_id) + for param in quota["compute"][0]: + if param in body["quota_set"]: + if (quota["compute"][0][param] == + str(body["quota_set"][param])): + actual_quota_count += 1 + body = cls.volume_quotas_client.show_quota_set(cust_id) + for param in quota["storage"][0]: + if param in body["quota_set"]: + if str(body["quota_set"][param]) == quota["compute"][0][param]: + actual_quota_count += 1 + body = cls.networks_quotas_client.show_quotas(cust_id) + for param in quota["network"][0]: + if param in body["quota_set"]: + if (quota["compute"][0][param] == + str(body["quota_set"][param])): + actual_quota_count += 1 + if expected_quota_count == actual_quota_count: + return True + else: + return False + + @classmethod + def _validate_users_on_cust_on_dcp_and_lcp(cls, post_body, cust_id): + default_users_req, region_users_req, \ + default_users_dcp, region_users_dcp, \ + users_lcp = [], [], [], [], [] + for user in post_body["regions"][0]["users"]: + region_users_req.append(user["id"]) + for user in post_body["users"]: + default_users_req.append(user["id"]) + expected_users_count = len(region_users_req) + len(default_users_req) + actual_users_count = 0 + lcp_body = cls.identity_client.list_tenant_users(cust_id) + for user in lcp_body["users"]: + users_lcp.append(user["id"]) + dcp_body = cls.client.get_customer(cust_id) + for user in dcp_body["regions"][0]["users"]: + region_users_dcp.append(user["id"]) + for user in dcp_body["users"]: + default_users_dcp.append(user["id"]) + for user in default_users_req: + if (user in users_lcp) and (user in default_users_dcp): + actual_users_count += 1 + for user in region_users_req: + if (user in users_lcp) and (user in region_users_dcp): + actual_users_count += 1 + if expected_users_count == actual_users_count: + return True + else: + return False + + @classmethod + def _del_cust_validate_deletion_on_dcp_and_lcp(cls, customer_id): + _, customer = cls.client.get_customer(customer_id) + regions_on_customer = [region for region in customer["regions"]] + if regions_on_customer: + region_name_on_customer = regions_on_customer[0]["name"] + cls._delete_region_from_customer_and_validate_deletion( + customer_id, region_name_on_customer) + cls.client.delete_customer(customer_id) + cls._wait_for_customer_deletion_on_dcp(customer_id) + cls._validate_customer_deletion_on_lcp(customer_id) + + @classmethod + def _delete_region_from_customer_and_validate_deletion( + cls, customer_id, rname): + _, region = cls.os_admin.rms_client.get_region(rname) + region_id = region["id"] + cls.client.delete_region_from_customer(customer_id, region_id) + # cls._wait_for_cust_status_on_dcp(customer_id, "no regions") + cls._wait_for_status(customer_id, "no regions") + _, body = cls.client.get_customer(customer_id) + regions_on_customer = [rgn for rgn in body["regions"]] + if regions_on_customer: + message = "Region %s failed to get deleted from customer %s " % ( + rname, customer_id) + raise exceptions.TempestException(message) + cls._validate_customer_deletion_on_lcp(customer_id) + + @classmethod + def _wait_for_customer_deletion_on_dcp(cls, customer_id): + _, body = cls.client.list_customers() + customer_list = body["customers"] + customer_ids = [customer["id"] + for customer in customer_list + if customer["id"] == customer_id] + start = int(time.time()) + while customer_ids: + time.sleep(cls.build_interval) + _, body = cls.client.list_customers()["customers"] + customer_list = body["customers"] + customer_ids = [customer["id"] + for customer in customer_list + if customer["id"] == customer_id] + if customer_ids: + customer_status = customer_ids[0]["status"] + if customer_status == 'Error': + message = "customer %s failed to get deleted and is in\ + error status" % customer_id + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ( + 'customer %s failed to get deleted within ' + 'the required time (%s s) and is in %s status.' + % (customer_id, cls.build_timeout, + customer_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_customer_deletion_on_lcp(cls, customer_id): + body = cls.identity_client.list_projects()["projects"] + customer_ids = [customer["id"] + for customer in body + if customer["id"] == customer_id] + if customer_ids: + message = "customer %s failed to get deleted on lcp" \ + % customer_id + raise exceptions.TempestException(message) + + @classmethod + def _update_cust_and_validate_status_on_dcp_and_lcp( + cls, customer_id, para, **kwargs): + body = cls.client.update_customer(customer_id, para, **kwargs) + if body["id"] == customer_id: + cls._wait_for_cust_status_on_dcp(customer_id, "Success") + cls._validate_cust_creation_on_lcp(customer_id) + else: + message = "customer %s not updated successfully" % customer_id + raise exceptions.TempestException(message) + return body diff --git a/ranger_tempest_plugin/tests/api/fms_base.py b/ranger_tempest_plugin/tests/api/fms_base.py new file mode 100755 index 0000000..df04ecb --- /dev/null +++ b/ranger_tempest_plugin/tests/api/fms_base.py @@ -0,0 +1,261 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 random +import time + +from oslo_log import log as logging +from ranger_tempest_plugin.tests.api import base + +from tempest import config +from tempest.lib import exceptions + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class FmsBaseOrmTest(base.BaseOrmTest): + credentials = ['admin', 'primary', 'alt'] + + # added setup_clients function by stew + @classmethod + def setup_clients(cls): + super(FmsBaseOrmTest, cls).setup_clients() + cls.client = cls.os_primary.fms_client + cls.flavors_client = cls.os_admin.flavors_client + cls.tenant_id = cls._get_project_id( + cls.os_primary.credentials.project_name) + cls.alt_tenant_id = cls._get_project_id( + cls.os_alt.credentials.project_name) + + @classmethod + def _get_flavor_params(cls, set_region=True, single_tenant=True): + post_body, region = {}, {} + region["name"] = CONF.identity.region + ram = random.randint(1, 4) * 1024 + swap = random.randint(1, 40) * 1024 + vcpus = random.randint(2, 40) + disk = random.randint(2, 102) + post_body["description"] = \ + "orm-plugin-BaseORMTest-flavor" + post_body["series"] = random.choice(CONF.ranger.flavor_series) + post_body["alias"] = "flavor_alias" + post_body["ram"] = str(ram) + post_body["vcpus"] = str(vcpus) + post_body["disk"] = str(disk) + post_body["swap"] = str(swap) + post_body["ephemeral"] = "1024" + post_body["regions"] = [region] if set_region else [] + post_body["visibility"] = "private" + post_body['tag'] = {'a': 'b', 'c': 'd'} + + if single_tenant: + post_body["tenants"] = [cls.tenant_id] + else: + post_body["tenants"] = [cls.tenant_id, cls.alt_tenant_id] + return post_body + + @classmethod + def _create_flv_and_validate_creation_on_dcp_and_lcp(cls, **kwargs): + """kwargs contain all field data needed in a flavor POST body: + + - name + - description + - alias + - ram + - vcpus + - disk + - swap + - ephemeral + - regions + - visibility + - tenants + """ + _, body = cls.client.create_flavor(**kwargs) + flavor = body["flavor"] + flavor_id = flavor["id"] + _, body = cls.client.get_flavor(flavor_id) + flavor_detail = body["flavor"] + if flavor_detail["vcpus"] == kwargs["vcpus"]: + if flavor_detail["regions"] == []: + flavor_status = "no regions" + else: + flavor_status = "Success" + flavor_id = flavor_detail["id"] + cls._wait_for_flavor_status_on_dcp(flavor_id, flavor_status) + cls._validate_flavor_creation_on_lcp(flavor_id) + return flavor + else: + message = "flavor %s not created successfully" % flavor_id + raise exceptions.TempestException(message) + + @classmethod + def _wait_for_flavor_status_on_dcp(cls, flavor_id, status): + _, body = cls.client.get_flavor(flavor_id) + flavor = body["flavor"] + flavor_status = flavor["status"] + start = int(time.time()) + while flavor_status != status: + time.sleep(cls.build_interval) + _, body = cls.client.get_flavor(flavor_id) + flavor = body["flavor"] + flavor_status = flavor["status"] + if flavor_status == 'Error': + message = ('flavor %s failed to reach %s status' + ' and is in ERROR status' % + (flavor_id, status)) + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message =\ + 'flavor %s failed to reach %s status within' + ' the required time (%s s) and is in' + '%s status.' % (flavor_id, status, + cls.build_timeout, flavor_status) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_flavor_creation_on_lcp(cls, flavor_id): + _, body = cls.client.list_flavors() + flavor = [flavor["id"] for flavor in body["flavors"] + if flavor["id"] == flavor_id] + if not flavor: + message = "flavor %s not in nova flavor list" % flavor_id + raise exceptions.TempestException(message) + + @classmethod + def _validate_flv_extraspecs_on_dcp_and_lcp(cls, flavor_id, + expected_specs): + expected_specs_count = len(expected_specs) + _, body = cls.client.get_flavor(flavor_id) + flavor_orm = body["flavor"] + flavor_lcp = cls.flavors_client.show_flavor(flavor_id)["flavor"] + + def _validate_extra_specs(flv): + actual_specs_count = 0 + actual_specs = {} + for spec in flv["extra-specs"]: + actual_specs[spec] = flv["extra-specs"][spec] + for spec in expected_specs: + if spec in actual_specs: + if expected_specs[spec] == actual_specs[spec]: + actual_specs_count += 1 + if expected_specs_count == actual_specs_count: + return True + else: + return False + if _validate_extra_specs(flavor_orm) and\ + _validate_extra_specs(flavor_lcp): + return True + else: + return False + + @classmethod + def _del_flv_and_validate_deletion_on_dcp_and_lcp(cls, flavor_id): + _, body = cls.client.get_flavor(flavor_id) + regions_on_flavor = [region for region in body["flavor"]["regions"]] + + if regions_on_flavor: + region_name_on_flavor = regions_on_flavor[0]["name"] + cls._delete_region_from_flavor_and_validate_deletion( + flavor_id, region_name_on_flavor) + cls.client.delete_flavor(flavor_id) + cls._wait_for_flavor_deletion_on_dcp(flavor_id) + cls._validate_flavor_deletion_on_lcp(flavor_id) + + @classmethod + def _delete_region_from_flavor_and_validate_deletion( + cls, flavor_id, rname): + _, body = cls.rms_client.get_region(rname) + region_id = body["id"] + cls.client.delete_region_from_flavor(flavor_id, region_id) + cls._wait_for_flavor_status_on_dcp(flavor_id, "no regions") + _, body = cls.client.get_flavor(flavor_id) + regions_on_flavor = body["flavor"]["regions"] + + if regions_on_flavor: + message = \ + "Region %s failed to get deleted from flavor %s " % ( + rname, flavor_id) + raise exceptions.TempestException(message) + cls._validate_flavor_deletion_on_lcp(flavor_id) + + @classmethod + def _wait_for_flavor_deletion_on_dcp(cls, flavor_id): + _, body = cls.client.list_flavors() + flavor_ids = [flavor["id"] for flavor in body["flavors"] + if flavor["id"] == flavor_id] + start = int(time.time()) + while flavor_ids: + time.sleep(cls.build_interval) + _, body = cls.client.list_flavors() + flavor_ids = [flavor["id"] for flavor in body["flavors"] + if flavor["id"] == flavor_id] + if flavor_ids: + flavor_status = flavor_ids[0]["status"] + if flavor_status == 'Error': + message = \ + 'Flavor %s failed to get deleted' + 'and is in error status' % \ + flavor_id + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ( + 'flavor %s failed to get deleted within ' + 'the required time (%s s) and is in %s status.' + % (flavor_id, cls.build_timeout, flavor_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_flavor_deletion_on_lcp(cls, flavor_id): + body = cls.flavors_client.list_flavors()["flavors"] + flavor_ids = [flavor["id"] for flavor in body + if flavor["id"] == flavor_id] + if flavor_ids: + flavor_status = cls.flavors_client.show_flavor( + flavor_id)["flavor"]["status"] + message = "flavor %s failed to get deleted and is in %s status" \ + % (flavor_id, flavor_status) + raise exceptions.TempestException(message) + + @classmethod + def _get_project_id(cls, project_name): + body = cls.identity_client.list_projects() + for project in body["projects"]: + if(project["name"] == project_name): + return project["id"] + message = ('project %s not found on projects list' % project_name) + raise exceptions.TempestException(message) + + @classmethod + def _get_expected_flavor_name(cls, post_body): + name = post_body["series"] + "." + "c" + \ + post_body["vcpus"] + "r" + \ + str(int(post_body["ram"]) / 1024) \ + + "d" + post_body["disk"] + "s" + \ + str(int(post_body["swap"]) / 1024) \ + + "e" + str(int(post_body["ephemeral"]) / 1024) + return name + + @classmethod + def _validate_flv_geometry_on_lcp(cls, flavor_id, post_body): + flv = cls.flavors_client.show_flavor(flavor_id)["flavor"] + if flv["vcpus"] == int(post_body["vcpus"]) and \ + flv["ram"] == post_body["ram"] and \ + flv["swap"] == int(post_body["swap"]) and \ + flv["disk"] == int(post_body["disk"]) and \ + flv["ephemeral"] == post_body["ephemeral"]: + return True + else: + return False diff --git a/ranger_tempest_plugin/tests/api/ims_base.py b/ranger_tempest_plugin/tests/api/ims_base.py new file mode 100755 index 0000000..676002b --- /dev/null +++ b/ranger_tempest_plugin/tests/api/ims_base.py @@ -0,0 +1,244 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 oslo_log import log as logging +from ranger_tempest_plugin.tests.api import base + +from tempest import config + +from tempest.common.utils import data_utils +from tempest.lib import exceptions + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class ImsBaseOrmTest(base.BaseOrmTest): + credentials = ['admin', 'primary', 'alt'] + + @classmethod + def setup_clients(cls): + super(ImsBaseOrmTest, cls).setup_clients() + # service clients + cls.client = cls.os_primary.ims_client + cls.rms_client = cls.os_primary.rms_client + cls.cms_client = cls.os_primary.cms_client + + # setup variables + cls.region_id = CONF.identity.region + cls.tenant_id = cls._get_project_id( + cls.os_primary.credentials.project_name) + cls.alt_tenant_id = cls._get_project_id( + cls.os_alt.credentials.project_name) + + @classmethod + def _get_image_params(cls, set_region=True, single_tenant=True, + set_private=True, set_enabled=True): + region, post_body = {}, {} + post_body["name"] = data_utils.rand_name( + "orm-plugin-TestTempestIms-image") + + post_body["url"] = CONF.ranger.image_url + post_body["disk-format"] = "qcow2" + post_body["container-format"] = "bare" + + region["name"] = cls.region_id + region["type"] = "single" + region["checksum"] = "7297321c2fa6424417a548c85edd6e98" + region["virtual_size"] = "None" + region["size"] = "38797312" + + # set enabled status to True or False based on set_enabled value + post_body["enabled"] = True if set_enabled else False + # add region for the image as needed + post_body["regions"] = [region] if set_region else [] + # create image with visibililty = "public" or "private" + post_body["visibility"] = "private" if set_private else "public" + # set image tags + post_body["tags"] = ["tag1", "tag2"] + + # add tenant for the image only if set_private + if set_private: + if single_tenant: + post_body["customers"] = [cls.tenant_id] + else: + post_body["customers"] = [cls.tenant_id, cls.alt_tenant_id] + else: + post_body["customers"] = [] + + return post_body + + @classmethod + def _get_project_id(cls, project_name): + body = cls.identity_client.list_projects() + for project in body["projects"]: + if(project["name"] == project_name): + return project["id"] + message = ('project %s not found on project list' % project_name) + raise exceptions.TempestException(message) + + @classmethod + def _create_img_and_validate_creation_on_dcp_and_lcp(cls, **kwargs): + _, body = cls.client.create_image(**kwargs) + image = body["image"] + image_id = image["id"] + + _, body = cls.client.get_image(image_id) + image_detail = body["image"] + if image_detail["name"] == kwargs["name"]: + if image_detail["regions"] == []: + image_status = "no regions" + else: + image_status = "Success" + + cls._wait_for_image_status_on_dcp(image_id, image_status) + return image + else: + message = ('image %s not created successfully' % kwargs["name"]) + raise exceptions.TempestException(message) + + @classmethod + def _wait_for_image_status_on_dcp(cls, image_id, status): + _, body = cls.client.get_image(image_id) + image_status = body["image"]["status"] + start = int(time.time()) + while image_status != status: + time.sleep(cls.build_interval) + _, body = cls.client.get_image(image_id) + image_status = body["image"]["status"] + if image_status == 'Error': + message = ('Image %s failed to reach %s status' + ' and is in ERROR status on orm' % + (image_id, status)) + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ('Image %s failed to reach %s' + ' status within ''the required time (%s s)' + ' on orm and is in %s status.' + % (image_id, status, + cls.build_timeout, + image_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_image_status_on_lcp(cls, image_id, status): + _, body = cls.client.list_images()["images"] + image_ids = [image["id"] for image in body] + if image_id not in image_ids: + message = ('Image %s not in image list on LCP' % image_id) + raise exceptions.TempestException(message) + else: + image_status = cls.client.show_image()["image"]["status"] + if image_status != status: + message = ('Image %s is in %s status instead of %s on LCP.' + % (image_id, image_status, status)) + raise exceptions.TempestException(message) + + @classmethod + def _update_img_validate_status_on_dcp_and_lcp(cls, image_id, para=None, + **kwargs): + if para: + cls.client.update_image(image_id, para, **kwargs) + else: + cls.client.update_image(image_id, **kwargs) + cls._wait_for_image_status_on_dcp(image_id, "Success") + cls._validate_image_status_on_lcp(image_id, "active") + + @classmethod + def _del_img_validate_deletion_on_dcp_and_lcp(cls, image_id): + _, body = cls.client.get_image(image_id) + image = body["image"] + + regions_on_image = [region for region in image["regions"]] + if regions_on_image: + region_id = regions_on_image[0]["name"] + cls._delete_region_from_image_and_validate_deletion( + image_id, region_id) + + cls.client.delete_image(image_id) + cls._validate_image_deletion_on_lcp(image_id) + + @classmethod + def _delete_region_from_image_and_validate_deletion(cls, image_id, + region_id): + cls.client.delete_region_from_image(image_id, region_id) + cls._wait_for_image_status_on_dcp(image_id, "no regions") + + @classmethod + def _wait_for_image_deletion_on_dcp(cls, image_id): + _, body = cls.client.list_images() + image_ids = [image["id"] for image in body["images"]] + if image_id in image_ids: + start = int(time.time()) + while image_id in image_ids: + time.sleep(cls.build_interval) + _, image_list = cls.client.list_images()["images"] + image_ids = [image["id"] + for image in image_list if image["id"] == + image_id] + if image_ids: + image_status = image_list[0]["status"] + _, body = cls.client.list_images()["images"] + image_ids = [image["id"] for image in body] + if image_id in image_ids: + image_status = image_ids[0]["status"] + if image_status == 'Error': + message = \ + 'Image %s failed to get deleted ' + 'and is in error status' % \ + image_id + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ('Image %s failed to get deleted within ' + 'the required time (%s s) on orm ' + 'and is in %s status.' + % (image_id, cls.build_timeout, image_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_image_deletion_on_lcp(cls, image_id): + _, body = cls.client.list_images() + image_ids = [image["id"] for image in body["images"] + if image["id"] == image_id] + if image_id in image_ids: + image_status = body["status"] + message = "image %s failed to get deleted and is in %s status" \ + % (image_id, image_status) + raise exceptions.TempestException(message) + + @classmethod + def _validate_custs_on_img_on_dcp_and_lcp(cls, image_id, + expected_customers): + expected_customers = sorted(expected_customers) + + _, actual_customers_orm = sorted( + cls.client.get_image(image_id)["customers"]) + members = cls.client.member_list(image_id)["members"] + actual_customers_lcp =\ + sorted([member["member_id"] for member in members]) + if actual_customers_orm != expected_customers: + message = ( + 'Incorrect customers on image on orm.' + 'expected customers = %s, actual customers = %s' + % (expected_customers, actual_customers_orm)) + raise exceptions.TempestException(message) + if actual_customers_lcp != expected_customers: + message = ( + 'Incorrect customers on image on orm.' + 'expected customers = %s, actual customers = %s' + % (expected_customers, actual_customers_lcp)) + raise exceptions.TempestException(message) diff --git a/ranger_tempest_plugin/tests/api/rms_base.py b/ranger_tempest_plugin/tests/api/rms_base.py new file mode 100755 index 0000000..ea10165 --- /dev/null +++ b/ranger_tempest_plugin/tests/api/rms_base.py @@ -0,0 +1,30 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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_log import log as logging +from ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class RmsBaseOrmTest(base.BaseOrmTest): + + @classmethod + def setup_clients(cls): + cls.client = cls.os_primary.rms_client + super(RmsBaseOrmTest, cls).setup_clients() diff --git a/ranger_tempest_plugin/tests/api/test_customers.py b/ranger_tempest_plugin/tests/api/test_customers.py new file mode 100755 index 0000000..85a99bd --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_customers.py @@ -0,0 +1,344 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 random + +from ranger_tempest_plugin.tests.api import cms_base +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions + +CONF = config.CONF + + +class TestTempestCms(cms_base.CmsBaseOrmTest): + + @classmethod + def resource_setup(cls): + cls.setup_customer = cls._get_customer_params() + cls.setup_customer_id = \ + cls._create_cust_validate_creation_on_dcp_and_lcp( + **cls.setup_customer) + + cls.bare_customer = cls._get_bare_customer_params() + cls.bare_customer_id = \ + cls._create_cust_validate_creation_on_dcp_and_lcp( + **cls.bare_customer) + + super(TestTempestCms, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls._del_cust_validate_deletion_on_dcp_and_lcp(cls.setup_customer_id) + cls._del_cust_validate_deletion_on_dcp_and_lcp(cls.bare_customer_id) + super(TestTempestCms, cls).resource_cleanup() + + @decorators.idempotent_id('6072c438-1e45-4c0b-97a6-e5127bd33d89') + def test_get_customer(self): + """Execute 'get_customer' using the following options + + - get customer by id (using cust_id parameter) + - get customer by name (using cust_name parameter) + """ + + # execute get_customer using cust_id and cust_name + for identifier in [self.setup_customer_id, + self.setup_customer['name']]: + _, body = self.client.get_customer(identifier) + self.assertIn(self.setup_customer_id, body['uuid']) + + @decorators.idempotent_id('6072c438-1e45-4c0b-97a6-e5127bd33d90') + def test_list_customers_with_filters(self): + """This function executes 'list customer' with all available filters + + - no filter (i.e. list all customers) + - filter by metadata key + - filter by region + - filter by default user id + - customer name contains a substring + - customer name starting with a string + """ + + # format filter parameter values + region_name = [ + region['name'] for region in self.setup_customer['regions']] + userid = [user['id'] for user in self.setup_customer['users']] + cust_name = self.setup_customer['name'] + substr_name = random.randint(0, len(cust_name)) + + # get the first key from metadata as the metadata key filter + metadata_key = list(self.setup_customer['metadata'].keys())[0] + + # define the list customer filters to be used for this test + no_filter = None + metadata_filter = {'metadata': metadata_key} + region_filter = {'region': region_name[0]} + user_filter = {'user': userid[0]} + contains_filter = {'contains': cust_name[substr_name:]} + startswith_filter = {'starts_with': cust_name[:substr_name]} + + # execute list_customers with the available filters + for list_filter in [no_filter, metadata_filter, region_filter, + user_filter, contains_filter, startswith_filter]: + _, body = self.client.list_customers(list_filter) + customers = [cust['id'] for cust in body['customers']] + self.assertIn(self.setup_customer_id, customers) + + @decorators.idempotent_id('ac132678-fdb6-4037-a310-813313044055') + def test_enable_customer(self): + # setup data for test case + post_body = self._get_customer_params(enabled=False) + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + + # update enabled status from 'False' to 'True' and validate update OK + _, body = self.client.get_customer(test_customer_id) + self.assertFalse(body['enabled']) + self.client.enable_customer(test_customer_id, True) + self._wait_for_status(test_customer_id, 'Success') + _, body = self.client.get_customer(test_customer_id) + self.assertTrue(body['enabled']) + + @decorators.idempotent_id('7dfd5f7e-7031-4ee1-b355-cd5cdeb21bd1') + def test_add_default_user(self): + # setup data for "add_default_user" test case; leave default + # and region users blank at the initial data creation + post_body = self._get_customer_params(default_users=False, + region_users=False) + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, body = self.client.get_customer(test_customer_id) + self.assertFalse(body['users']) + self.assertFalse(body["regions"][0]['users']) + + # now add a default user, then validate that new user is added to + # BOTH default user AND region user successfully + post_default_user = self._get_user_params() + new_user_id = post_default_user[0]["id"] + _, body = self.client.add_default_user(test_customer_id, + *post_default_user) + self._wait_for_status(test_customer_id, 'Success') + _, body = self.client.get_customer(test_customer_id) + self.assertIn(new_user_id, [x['id'] for x in body['users']]) + self.assertIn(new_user_id, [x['id'] + for x in body['regions'][0]['users']]) + + @decorators.idempotent_id('699e8487-035e-4ae0-97b4-ca51b9a08aea') + def test_delete_default_user(self): + # setup data for delete_default_user test case + post_body = self._get_customer_params() + default_user_id = post_body["users"][0]["id"] + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, body = self.client.get_customer(test_customer_id) + self.assertEqual(default_user_id, body['users'][0]['id']) + self.assertIn(default_user_id, + [x['id'] for x in body['regions'][0]['users']]) + + # delete default user and validate operation success by confirming + # user id no longer in both default users and region users list + _, body = self.client.delete_default_user(test_customer_id, + default_user_id) + self._wait_for_status(test_customer_id, 'Success') + _, body = self.client.get_customer(test_customer_id) + self.assertFalse(body['users']) + self.assertNotIn(default_user_id, + [x['id'] for x in body['regions'][0]['users']]) + + @decorators.idempotent_id('48ffd49f-2b36-40b4-b1b4-0c805b7ba7c2') + def test_replace_default_user(self): + # setup data for "replace_default_user" test case; no need to + # set region user as default user will also be assigned to it + post_body = self._get_customer_params(region_users=False) + default_user_id = post_body["users"][0]["id"] + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, body = self.client.get_customer(test_customer_id) + self.assertIn(default_user_id, [x['id'] for x in body['users']]) + self.assertEqual(body['users'], body['regions'][0]['users']) + + # replace default user + put_default_user = self._get_user_params(alt=True) + updated_user_id = put_default_user[0]["id"] + _, body = self.client.replace_default_user(test_customer_id, + *put_default_user) + self._wait_for_status(test_customer_id, 'Success') + + # validate that BOTH the customer default user and region user + # are replaced with the new default_user successfully + _, body = self.client.get_customer(test_customer_id) + self.assertEqual(len(body['users']), + len(body['regions'][0]['users'])) + self.assertIn(updated_user_id, [x['id'] for x in body['users']]) + self.assertEqual(body['users'], body['regions'][0]['users']) + + @decorators.idempotent_id('07a631f9-3aa5-4797-9ead-4531ced89e2a') + def test_add_region_user(self): + # We are leaving both default and region users as blank to ensure + # region user is empty on initial data creation for this test case + post_body = self._get_customer_params(region_users=False, + default_users=False) + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, body = self.client.get_customer(test_customer_id) + # confirm that the region users body is empty after data creation + self.assertFalse(body["regions"][0]["users"]) + + # now we shall add user to regions[0] + post_region_user = self._get_user_params() + _, body = self.client.add_region_user(test_customer_id, + CONF.identity.region, + *post_region_user) + self._wait_for_status(test_customer_id, 'Success') + _, customer = self.client.get_customer(test_customer_id) + # validate that the region user is no longer empty after the add + self.assertTrue(customer["regions"][0]["users"]) + + @decorators.idempotent_id('9b2b3af8-2444-4143-a9e6-78c33b36c823') + def test_delete_region_user(self): + # To test delete_region_users scenario, leave default user blank + # for the test customer data, or else default user info will be + # added as region user as well + post_body = self._get_customer_params(default_users=False) + region_user_id = post_body["regions"][0]["users"][0]["id"] + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, body = self.client.get_customer(test_customer_id) + self.assertTrue(body["regions"][0]["users"]) + + # delete the user from the region, then validate operation success + _, body = self.client.delete_region_user(test_customer_id, + CONF.identity.region, + region_user_id) + self._wait_for_status(test_customer_id, 'Success') + _, customer = self.client.get_customer(test_customer_id) + # validate that the region user is now empty + self.assertFalse(customer["regions"][0]["users"]) + + @decorators.idempotent_id('0ca59977-ef29-46b9-be92-14980a12c573') + def test_replace_region_user(self): + post_body = self._get_customer_params() + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + + # update region user then confirm that update is successful + put_region_user = self._get_user_params(alt=True) + new_region_user_id = put_region_user[0]["id"] + + _, body = self.client.replace_region_user(test_customer_id, + CONF.identity.region, + *put_region_user) + self._wait_for_status(test_customer_id, 'Success') + _, customer = self.client.get_customer(test_customer_id) + self.assertIn(new_region_user_id, [x['id'] + for x in customer["regions"][0]['users']]) + + @decorators.idempotent_id('f1444965-c711-438d-ab86-a2412acbe8e0') + def test_replace_metadata(self): + metadata = {'metadata': {'replace_key': 'replace_value'}} + _, body = self.client.replace_metadata(self.setup_customer_id, + metadata) + self._wait_for_status(self.setup_customer_id, 'Success') + _, body = self.client.list_customers({'metadata': 'replace_key'}) + self.assertIn(self.setup_customer_id, + [x['id'] for x in body['customers']]) + + @decorators.idempotent_id('80713a87-8e95-481f-a198-6b4515d48362') + def test_add_metadata(self): + metadata = {'metadata': {'add_key': 'add_value'}} + _, body = self.client.add_metadata(self.setup_customer_id, + metadata) + self._wait_for_status(self.setup_customer_id, 'Success') + _, body = self.client.list_customers({'metadata': 'add_key'}) + self.assertIn(self.setup_customer_id, + [x['id'] for x in body['customers']]) + + @decorators.idempotent_id('19a6bbe2-92b9-46be-95b7-aa0d9f247d88') + def test_add_regions(self): + region = self._get_region_params() + _, body = self.client.add_regions(self.bare_customer_id, + region) + self._wait_for_status(self.setup_customer_id, 'Success') + _, body = self.client.list_customers({'region': region[0]['name']}) + self.assertIn(self.setup_customer_id, + [x['id'] for x in body['customers']]) + + @decorators.idempotent_id('09a43bc1-439e-4497-a026-f2c3de451d29') + def test_delete_regions(self): + # setup data for delete_region + post_body = self._get_customer_params() + region_name = post_body["regions"][0]["name"] + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + _, customer = self.client.get_customer(test_customer_id) + self.assertTrue(customer["regions"]) + _, body = self.client.delete_region_from_customer(test_customer_id, + region_name) + self._wait_for_status(test_customer_id, 'no regions') + _, customer = self.client.get_customer(test_customer_id) + self.assertFalse(customer["regions"]) + + @decorators.idempotent_id('c7a24667-2a99-41ac-a42d-6c1163ef48af') + def test_create_customer(self): + post_body = self._get_customer_params(quota=False) + test_cust_name = post_body['name'] + _, body = self.client.create_customer(**post_body) + test_customer_id = body['customer']['id'] + self.addCleanup(self._del_cust_validate_deletion_on_dcp_and_lcp, + test_customer_id) + self._wait_for_status(test_customer_id, 'Success') + _, body = self.client.get_customer(test_cust_name) + self.assertIn(test_customer_id, body['uuid']) + + @decorators.idempotent_id('43785f87-27d5-408d-997f-de602caeb698') + def test_replace_customer(self): + customer = self._get_bare_customer_params() + customer['name'] = self.setup_customer['name'] + customer['regions'] = [{'name': CONF.identity.region}] + _, body = self.client.update_customer(self.setup_customer_id, + customer) + self._wait_for_status(self.setup_customer_id, 'Success') + _, body = self.client.get_customer(self.setup_customer_id) + self.assertExpected(customer, body, ['name', 'regions']) + for region in customer['regions']: + self.assertIn(region['name'], [x['name'] for x in body['regions']]) + + @decorators.idempotent_id('e8b9077a-d45c-4e24-a433-e7dfa07486b9') + def test_delete_customer(self): + # setup data for test case + post_body = self._get_customer_params() + test_customer_id = self._create_cust_validate_creation_on_dcp_and_lcp( + **post_body) + + # delete the data and do get_customer to ensure 404-NotFound response + self._del_cust_validate_deletion_on_dcp_and_lcp(test_customer_id) + self.assertRaises(exceptions.NotFound, self.client.get_customer, + test_customer_id) diff --git a/ranger_tempest_plugin/tests/api/test_customers_negative.py b/ranger_tempest_plugin/tests/api/test_customers_negative.py new file mode 100755 index 0000000..ac530fb --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_customers_negative.py @@ -0,0 +1,42 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF + + +class TestTempestCmsNegative(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestCmsNegative, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestCmsNegative, cls).setup_clients() + cls.client = cls.cmsclient + + @classmethod + def resource_setup(cls): + cls.set_role_to_admin() + super(TestTempestCmsNegative, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls.delete_role_to_admin() + super(TestTempestCmsNegative, cls).resource_cleanup() diff --git a/ranger_tempest_plugin/tests/api/test_flavors.py b/ranger_tempest_plugin/tests/api/test_flavors.py new file mode 100755 index 0000000..38083fb --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_flavors.py @@ -0,0 +1,361 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 json + +from ranger_tempest_plugin.tests.api import fms_base +from tempest import config + +from tempest.lib import decorators +from tempest.lib import exceptions + +from tempest.lib.common.utils import data_utils + +CONF = config.CONF + + +class TestTempestFms(fms_base.FmsBaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestFms, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestFms, cls).setup_clients() + cls.rms_client = cls.os_primary.rms_client + + @classmethod + def resource_setup(cls): + + # create flavor then save off flavor_id for use in test cases + body = cls._get_flavor_params() + cls.flavor = cls._create_flv_and_validate_creation_on_dcp_and_lcp( + **body) + + # these variables will be used to test list filters + cls.flavor_id = cls.flavor['id'] + cls.flavor_name = cls.flavor['name'] + cls.visibility = cls.flavor['visibility'] + cls.series = cls.flavor['series'] + cls.tenant_id = cls.tenant_id + cls.region_id = CONF.identity.region + cls.alias = cls.flavor['alias'] + cls.dflt_ex_specs = cls.flavor['extra-specs'] + + cls.tags = cls.flavor['tag'] + + # add custom extra specs + cls.custom_es = {'os_extra_specs': {'g': 'guava', 'h': 'honeydew'}} + cls.client.add_extra_specs(cls.flavor_id, cls.custom_es) + cls._wait_for_flavor_status_on_dcp(cls.flavor_id, 'Success') + + super(TestTempestFms, cls).resource_setup() + + def _get_flavor_details(self, flavor_id): + _, body = self.client.get_flavor(flavor_id) + return body["flavor"] + + def _data_setup(self, post_body): + flavor = self._create_flv_and_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_flv_and_validate_deletion_on_dcp_and_lcp, + flavor["id"]) + return flavor + + def _exec_tags_function(self, flavor_id, req_json, action, para): + + if action == 'add': + _, body = self.client.add_tags(flavor_id, req_json) + + elif action == 'update': + _, body = self.client.update_tags(flavor_id, req_json) + + elif action == 'delete': + _, body = self.client.delete_tags(flavor_id, para) + + self._wait_for_flavor_status_on_dcp(flavor_id, 'Success') + + def _exec_ex_spec_function(self, flavor_id, es_body, action, para): + if action == 'add': + _, body = self.client.add_extra_specs(flavor_id, es_body) + + elif action == 'update': + _, body = self.client.update_extra_specs(flavor_id, es_body) + + elif action == 'delete': + _, body = self.client.delete_extra_specs(flavor_id, para) + + self._wait_for_flavor_status_on_dcp(flavor_id, 'Success') + + def _restore_default_tags(self, flavor_id): + tag_body = {"tags": self.tags} + _, body = self.client.update_tags(flavor_id, tag_body) + self._wait_for_flavor_status_on_dcp(flavor_id, 'Success') + + _, tag_body = self.client.get_tags(flavor_id) + self.assertDictEqual(self.tags, body.get("tags")) + + def _restore_custom_es(self, flavor_id): + _, body = self.client.update_extra_specs(flavor_id, self.custom_es) + self._wait_for_flavor_status_on_dcp(flavor_id, 'Success') + + _, es = self.client.get_extra_specs(flavor_id) + es_expected = self.custom_es.get("os_extra_specs") + es_expected.update(self.dflt_ex_specs) + self.assertDictEqual(es_expected, es.get("os_extra_specs")) + + @classmethod + def resource_cleanup(cls): + # cls.delete_role_to_admin() + flavor_id = cls.flavor['id'] + cls._del_flv_and_validate_deletion_on_dcp_and_lcp(flavor_id) + super(TestTempestFms, cls).resource_cleanup() + + @decorators.idempotent_id('2a4481cd-acce-4a5d-af7c-940222a6238b') + def test_get_flavor(self): + """Execute get_flavor using flavor_id / flavor_name""" + for para in [self.flavor_id, self.flavor_name]: + _, body = self.client.get_flavor(para) + self.assertIn(self.flavor_id, body["flavor"]["id"]) + + @decorators.idempotent_id('c46a503a-951c-4d00-afaa-46076b54db16') + def test_list_flavor_with_filters(self): + """This function executes 'list flavors' with the following filters: + + - None (no filters, i.e. list all flavors) + - alias filter + - region filter + - visibility filter + - 'contains' filter + - 'starts_with' filter + - 'series' filter + - "tenant" filter + """ + # for use by the 'constains and 'starts_with' filter + str_index1 = data_utils.rand_int_id(0, len(self.flavor_name) - 1) + str_index2 = data_utils.rand_int_id(str_index1 + 1, + len(self.flavor_name)) + + # define the list flavors filters to be used for this test + alias_filter = "?alias=%s" % self.alias + region_filter = "?region=%s" % self.region_id + visibility_filter = "?visibility=%s" % self.visibility + contains_filter = '?contains=%s' \ + % self.flavor_name[str_index1:str_index2] + startswith_filter = "?starts_with=%s" % self.flavor_name[:str_index2] + series_filter = "?series=%s" % self.series + tenant_filter = "?tenant=%s" % self.tenant_id + + for list_filter in [None, region_filter, visibility_filter, + alias_filter, contains_filter, startswith_filter, + series_filter, tenant_filter]: + _, body = self.client.list_flavors(list_filter) + flavor_ids = [flvr["id"] for flvr in body["flavors"]] + self.assertIn(self.flavor_id, flavor_ids) + + @decorators.idempotent_id('7b9d1f91-a8a4-458d-aaad-a98b5bf033b4') + def test_create_flavor(self): + post_body = self._get_flavor_params() + # call client create_flavor and wait till status equals 'Success' + _, body = self.client.create_flavor(**post_body) + test_flvr_id = body["flavor"]['id'] + self._wait_for_flavor_status_on_dcp(test_flvr_id, 'Success') + + # do not forget to add this account to addCleanUp + self.addCleanup(self._del_flv_and_validate_deletion_on_dcp_and_lcp, + test_flvr_id) + + # verify flavor record created successfully + flavor = self._get_flavor_details(test_flvr_id) + self.assertEqual(flavor["visibility"], "private") + self.assertEqual(flavor["regions"][0]["name"], CONF.identity.region) + self.assertEqual(flavor["status"], "Success") + + @decorators.idempotent_id('4cad10ce-67d2-4633-b347-2c16783a31b9') + def test_add_flvr_tags(self): + # test add_tags command with two sets of key:values + add_tag_body = {"tags": {"aa": "bb", "cc": "dd"}} + self._exec_tags_function(self.flavor_id, add_tag_body, 'add', None) + _, tag_body = self.client.get_tags(self.flavor_id) + + subset = {k: v for k, v in tag_body.get("tags").items() + if k in add_tag_body.get("tags")} + + self.assertDictEqual(add_tag_body.get("tags"), subset) + + @decorators.idempotent_id('db8e5c0f-0041-45d4-9939-e079296123d8') + def test_replace_flvr_tags(self): + # test replace_tags command + replace_tag_body = {"tags": {"ee": "ff", "gg": "hh"}} + self._exec_tags_function(self.flavor_id, replace_tag_body, + 'update', None) + self.addCleanup(self._restore_default_tags, self.flavor_id) + _, tag_body = self.client.get_tags(self.flavor_id) + self.assertDictEqual(replace_tag_body.get("tags"), + tag_body.get("tags")) + + @decorators.idempotent_id('e0a0eca6-e120-45ab-a1a4-f5b95fdf97f1') + def test_delete_flvr_tag(self): + # test delete_tag command - delete a tag from tags body + delete_tag_key = "a" + self._exec_tags_function(self.flavor_id, None, 'delete', + delete_tag_key) + # do get_tag and tries to retrieve the deleted tag + _, tag_body = self.client.get_tags(self.flavor_id) + tag_present = tag_body.get("tags").get(delete_tag_key, None) + self.assertIsNone(tag_present) + + @decorators.idempotent_id('9c511020-53ed-4139-8c53-451cb0ea8c75') + def test_delete_all_flvr_tags(self): + # ensure there is at least a tag + _, tag_body = self.client.get_tags(self.flavor_id) + self.assertTrue(True if tag_body.get("tags") else False) + + # test delete_all_tags command - run get_tag again and confirm + # that the tag dict is now empty + self._exec_tags_function(self.flavor_id, None, 'delete', None) + self.addCleanup(self._restore_default_tags, self.flavor_id) + _, tag_body = self.client.get_tags(self.flavor_id) + # assert that tag_body contains nothing + self.assertFalse(tag_body["tags"]) + + @decorators.idempotent_id('ec74d68f-b42a-41a8-9685-ff5eca25ea0c') + def test_add_delete_flvr_region(self): + # setup data to add region + post_body = self._get_flavor_params(set_region=False) + flavor = self._data_setup(post_body) + test_flvr_id = flavor['id'] + + post_region_body = '{"regions": [{"name": "%s"}]}' % (self.region_id) + post_region_body = json.loads(post_region_body) + _, body = self.client.add_flvr_regions(test_flvr_id, + post_region_body) + self._wait_for_flavor_status_on_dcp(test_flvr_id, 'Success') + _, body = self.client.get_flavor(test_flvr_id) + self.assertEqual(body["flavor"]["regions"][0]["name"], + post_region_body["regions"][0]["name"]) + + # remove added region and then check to confirm flavor status + _, body = self.client.delete_flvr_region(test_flvr_id, + self.region_id) + # flavor status must show 'no regions' when it has no region assigned + self._wait_for_flavor_status_on_dcp(test_flvr_id, 'no regions') + # flavor region is now empty after the lone region was removed + _, body = self.client.get_flavor(test_flvr_id) + self.assertTrue(len(body["flavor"]["regions"]) == 0) + + @decorators.idempotent_id('71404409-5d95-472c-8dac-b49a1c0c4b37') + def test_add_flvr_extra_specs(self): + # get extra specs before the add + _, es_body = self.client.get_extra_specs(self.flavor_id) + es_expected = es_body.get("os_extra_specs") + + # add custom extra specs + add_es_body = {"os_extra_specs": {"a": "apple", "b": "banana"}} + self._exec_ex_spec_function(self.flavor_id, add_es_body, 'add', None) + _, es = self.client.get_extra_specs(self.flavor_id) + + # assert extra specs add results match expected + es_expected.update(add_es_body["os_extra_specs"]) + self.assertDictEqual(es_expected, es.get("os_extra_specs")) + + @decorators.idempotent_id('043948fd-125b-4d96-bf40-42464066a7e1') + def test_update_flvr_extra_specs(self): + # add custom extra spec using update_extra_spec + replace_es_body = {"os_extra_specs": {"z": "zebra", "x": "xray"}} + self._exec_ex_spec_function(self.flavor_id, replace_es_body, 'update', + None) + self.addCleanup(self._restore_custom_es, self.flavor_id) + + # assert extra specs update results match expected + _, flvr_ex_specs = self.client.get_extra_specs(self.flavor_id) + + es_expected = replace_es_body.get("os_extra_specs") + es_expected.update(self.dflt_ex_specs) + self.assertDictEqual(es_expected, flvr_ex_specs.get("os_extra_specs")) + + @decorators.idempotent_id('df83e2cd-d202-4b2f-8459-391a73067ec5') + def test_delete_flvr_extra_spec(self): + # get extra specs before the delete + _, es_body = self.client.get_extra_specs(self.flavor_id) + + # now delete one of the custom extra specs + delete_es_key_h = "h" + self._exec_ex_spec_function(self.flavor_id, None, 'delete', + delete_es_key_h) + + # assert extra specs update results match expected + es_body["os_extra_specs"].pop(delete_es_key_h) + _, flvr_ex_specs = self.client.get_extra_specs(self.flavor_id) + self.assertDictEqual(es_body["os_extra_specs"], + flvr_ex_specs.get("os_extra_specs")) + + @decorators.idempotent_id('e3fc7ce3-c8fe-4805-8ad3-7be2c94fe7ad') + def test_delete_all_flvr_extra_specs(self): + # run delete ALL extra specs - note that this will only + # delete custom extra specs, NOT the default extra specs + self._exec_ex_spec_function(self.flavor_id, None, 'delete', None) + self.addCleanup(self._restore_custom_es, self.flavor_id) + _, flvr_ex_specs = self.client.get_extra_specs(self.flavor_id) + # assert that flavor extra specs now contains only + # the default extra specs + self.assertDictEqual(self.dflt_ex_specs, + flvr_ex_specs.get("os_extra_specs")) + + @decorators.idempotent_id('187195b5-adfb-4c73-a2f5-42117021f5f2') + def test_add_delete_flvr_tenant(self): + # setup data for test case + post_body = self._get_flavor_params() + flavor = self._data_setup(post_body) + test_flvr_id = flavor['id'] + + # check that flavor contains one tenant before testing add_flvr_tenant + flavor = self._get_flavor_details(test_flvr_id) + self.assertEqual(len(flavor["tenants"]), 1) + self.assertEqual(flavor["tenants"][0], self.tenant_id) + + # test add_flvr_tenant by adding one more tenant + post_tenant_body = '{"tenants": ["%s"]}' % (self.alt_tenant_id) + post_tenant_body = json.loads(post_tenant_body) + _, body = self.client.add_flvr_tenants(test_flvr_id, post_tenant_body) + self._wait_for_flavor_status_on_dcp(test_flvr_id, 'Success') + + # get flavor on same flavor id and confirm we have two tenants now + flavor = self._get_flavor_details(test_flvr_id) + self.assertEqual(len(flavor["tenants"]), 2) + self.assertIn(self.alt_tenant_id, flavor["tenants"]) + + # delete one tenant, then wait until status = 'Success' + _, body = self.client.delete_flvr_tenant(test_flvr_id, + self.alt_tenant_id) + self._wait_for_flavor_status_on_dcp(test_flvr_id, 'Success') + # get flavor to confirm flavor["tenants"] now shows one tenant only + flavor = self._get_flavor_details(test_flvr_id) + self.assertEqual(len(flavor["tenants"]), 1) + self.assertEqual(flavor["tenants"][0], self.tenant_id) + + @decorators.idempotent_id('36958bba-673e-4397-85a9-fddb01e9ca0d') + def test_delete_flavor(self): + # setup data for test case + post_body = self._get_flavor_params() + flavor = self._create_flv_and_validate_creation_on_dcp_and_lcp( + **post_body) + test_flvr_id = flavor['id'] + + # delete the data and do get_flavor to ensure 404-NotFound response + self._del_flv_and_validate_deletion_on_dcp_and_lcp(test_flvr_id) + self.assertRaises(exceptions.NotFound, self.client.get_flavor, + test_flvr_id) diff --git a/ranger_tempest_plugin/tests/api/test_flavors_negative.py b/ranger_tempest_plugin/tests/api/test_flavors_negative.py new file mode 100755 index 0000000..da1e72a --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_flavors_negative.py @@ -0,0 +1,44 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF + + +class TestTempestFmsNegative(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestFmsNegative, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestFmsNegative, cls).setup_clients() + cls.client = cls.fmsclient + + @classmethod + def resource_setup(cls): + cls.set_role_to_admin() + super(TestTempestFmsNegative, cls).resource_setup() + cls.env_name = cls.env_name + cls.tenant_id = cls.tenant_id + + @classmethod + def resource_cleanup(cls): + cls.delete_role_to_admin() + super(TestTempestFmsNegative, cls).resource_cleanup() diff --git a/ranger_tempest_plugin/tests/api/test_images.py b/ranger_tempest_plugin/tests/api/test_images.py new file mode 100755 index 0000000..df0427a --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_images.py @@ -0,0 +1,260 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import ims_base +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions + +CONF = config.CONF + + +class TestTempestIms(ims_base.ImsBaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestIms, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestIms, cls).setup_clients() + + @classmethod + def resource_setup(cls): + # setup public image for tempest testing + cls.image_params = \ + cls._get_image_params(set_private=False) + cls.public_image = \ + cls._create_img_and_validate_creation_on_dcp_and_lcp( + **cls.image_params) + + # setup private image for tempest testing + cls.image_params = cls._get_image_params(set_enabled=False) + cls.private_image = \ + cls._create_img_and_validate_creation_on_dcp_and_lcp( + **cls.image_params) + + super(TestTempestIms, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls._del_img_validate_deletion_on_dcp_and_lcp(cls.public_image['id']) + cls._del_img_validate_deletion_on_dcp_and_lcp(cls.private_image['id']) + super(TestTempestIms, cls).resource_cleanup() + + def _data_setup(self, post_body): + image = self._create_img_and_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_img_validate_deletion_on_dcp_and_lcp, + image["id"]) + # only check for Success image status if "regions" is not empty + if image["regions"]: + self._wait_for_image_status_on_dcp(image["id"], 'Success') + + return image + + @decorators.idempotent_id('2b1bb28b-4151-4e75-ae1b-d21089c3418c') + def test_get_image(self): + """Execute 'get_image' using the following options: + + - get image by id (using cust_id parameter) + - get image by name (using cust_name parameter) + """ + + # execute get_image using image ID and iamge_name + for identifier in [self.public_image['id'], self.public_image['name']]: + _, body = self.client.get_image(identifier) + self.assertIn(self.public_image['id'], body['image']['id']) + + @decorators.idempotent_id('6072c438-1e45-4c0b-97a6-e5127bd33d90') + def test_list_images_with_filters(self): + """This function executes 'list customer' with all available filters: + + - no filter (i.e. list all images) + - filter by region + - filter by customer + - filter by visibility (public/private) + """ + + # define the list customer filters to be used for this test + no_filter = None + customer_filter = "?customer=%s" % self.tenant_id + region_filter = "?region=%s" % self.region_id + public_filter = "?visibility=public" + private_filter = "?visibility=private" + + # list public images + _, body = self.client.list_images(public_filter) + image_ids = [img['id'] for img in body['images']] + self.assertIn(self.public_image['id'], image_ids) + + # list private images + _, body = self.client.list_images(private_filter) + image_ids = [img['id'] for img in body['images']] + self.assertIn(self.private_image['id'], image_ids) + + # execute list_customers with the rest of the filters + for list_filter in [no_filter, region_filter, + customer_filter]: + _, body = self.client.list_images(list_filter) + images = [image['id'] for image in body['images']] + self.assertIn(self.private_image['id'], images) + + @decorators.idempotent_id('4435fef4-49a9-435b-8463-cf8a1e0b7cd8') + def test_disable_image(self): + # disable self.public_image and check if request is successful + self.client.enabled_image(self.public_image['id'], False) + self._wait_for_image_status_on_dcp(self.public_image['id'], 'Success') + _, body = self.client.get_image(self.public_image['id']) + image = body["image"] + + # assert that the image["enabled"] value is 'False' + self.assertTrue(not image['enabled']) + + @decorators.idempotent_id('f32a13e3-6f38-423b-a616-09c8d4e1c277') + def test_enable_image(self): + # enable self.private_image and check if request is successful + self.client.enabled_image(self.private_image['id'], True) + self._wait_for_image_status_on_dcp(self.private_image['id'], 'Success') + _, body = self.client.get_image(self.private_image['id']) + image = body["image"] + + # assert that the image["enabled"] value is 'True' + self.assertTrue(image['enabled']) + + @decorators.idempotent_id('cb9e3022-00d7-4a21-bdb2-67d3cd15a4f8') + def test_add_delete_image_region(self): + # skip region assignment in data setup + post_body = self._get_image_params(set_region=False) + image = self._data_setup(post_body) + test_image_id = image['id'] + + # add region to image then check to confirm image status = "Success" + self.client.add_region_to_image(test_image_id, self.region_id) + # image status must show 'Success' when assigned to a region + self._wait_for_image_status_on_dcp(test_image_id, 'Success') + + # check that region is successfully added + _, body = self.client.get_image(test_image_id) + image = body["image"] + self.assertEqual(image["regions"][0]["name"], self.region_id) + + # delete the region then check to confirm image status = "no regions" + _, body = self.client.delete_region_from_image(test_image_id, + self.region_id) + self._wait_for_image_status_on_dcp(test_image_id, 'no regions') + + # image region array should be empty after the region was removed + _, body = self.client.get_image(test_image_id) + image = body["image"] + self.assertFalse(image["regions"]) + + @decorators.idempotent_id('0ee68189-66a8-4213-ad68-bc12991c174a') + def test_add_delete_image_tenant(self): + # add alt tenant to self.private_image & check if status = "Success" + self.client.add_customer_to_image(self.private_image['id'], + self.alt_tenant_id) + self._wait_for_image_status_on_dcp(self.private_image['id'], + 'Success') + + # check that alt tenant successfully added to image tenants array + _, body = self.client.get_image(self.private_image['id']) + image = body["image"] + self.assertEqual(len(image["customers"]), 2) + self.assertIn(self.alt_tenant_id, image['customers']) + + # now delete alt_tenant_id and ensure operation is successful + _, body = self.client.delete_customer_from_image( + self.private_image['id'], + self.alt_tenant_id) + self._wait_for_image_status_on_dcp(self.private_image['id'], 'Success') + + # image region array should no longer contain alt tenant + _, body = self.client.get_image(self.private_image['id']) + image = body["image"] + self.assertNotIn(self.alt_tenant_id, image['customers']) + + @decorators.idempotent_id('bac99348-6b13-4b30-958b-3c039b27eda3') + def test_update_image_tenant(self): + # replace current tenant in self.private_image with alt tenant + self.client.update_customer(self.private_image['id'], + self.alt_tenant_id) + self._wait_for_image_status_on_dcp(self.private_image['id'], 'Success') + + # check that image tenants array contains only alt tenant + _, body = self.client.get_image(self.private_image['id']) + image = body["image"] + self.assertEqual(len(image["customers"]), 1) + self.assertIn(self.alt_tenant_id, image['customers']) + + @decorators.idempotent_id('0331e02a-ab52-4341-b676-a02462244277') + def test_create_image(self): + post_body = self._get_image_params() + # call client create_IMAGE and wait till status equals 'Success' + _, body = self.client.create_image(**post_body) + image = body["image"] + test_image_id = image["id"] + self._wait_for_image_status_on_dcp(test_image_id, 'Success') + + # do not forget to add this account to addCleanUp + self.addCleanup(self._del_img_validate_deletion_on_dcp_and_lcp, + test_image_id) + # verify image record created successfully + _, body = self.client.get_image(test_image_id) + image = body["image"] + self.assertEqual(image["regions"][0]["name"], CONF.identity.region) + + @decorators.idempotent_id('01160918-e217-401d-a6a0-e7992ab76e41') + def test_update_image(self): + region = {} + post_body = self._get_image_params(set_region=False) + image = self._data_setup(post_body) + test_image_id = image['id'] + + # setup region and change 'enabled', 'customers' properties + region["name"] = self.region_id + region["type"] = "single" + region["checksum"] = "7297321c2fa6424417a548c85edd6e98" + region["virtual_size"] = "None" + region["size"] = "38797312" + post_body["regions"] = [region] + post_body["enabled"] = False + post_body["customers"] = [self.alt_tenant_id] + # empty tags list + post_body["tags"] = [] + + _, body = self.client.update_image(test_image_id, para=None, + **post_body) + self._wait_for_image_status_on_dcp(test_image_id, 'Success') + # verify image record updated successfully + _, body = self.client.get_image(test_image_id) + image = body["image"] + self.assertEqual(image["regions"][0]["name"], CONF.identity.region) + self.assertIn(self.alt_tenant_id, image['customers']) + self.assertFalse(image['enabled']) + self.assertFalse(image['tags']) + + @decorators.idempotent_id('23e2e7e2-5b19-4c66-b35c-7c686a986627') + def test_delete_image(self): + # setup data for test case + post_body = self._get_image_params() + image = self._create_img_and_validate_creation_on_dcp_and_lcp( + **post_body) + test_image_id = image['id'] + + # delete the data and do get_image to ensure 404-NotFound response + self._del_img_validate_deletion_on_dcp_and_lcp(test_image_id) + self.assertRaises(exceptions.NotFound, self.client.get_image, + test_image_id) diff --git a/ranger_tempest_plugin/tests/api/test_images_negative.py b/ranger_tempest_plugin/tests/api/test_images_negative.py new file mode 100755 index 0000000..b06f40c --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_images_negative.py @@ -0,0 +1,47 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF + + +class TestTempestImsNegative(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestImsNegative, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestImsNegative, cls).setup_clients() + cls.client = cls.imsclient + cls.servers_client = cls.servers_client + + @classmethod + def resource_setup(cls): + cls.set_role_to_admin() + super(TestTempestImsNegative, cls).resource_setup() + cls.env_name = cls.env_name + cls.tenant_id = cls.tenant_id + cls.flavor_ref = CONF.compute.flavor_ref + cls.network_ref = CONF.network.public_network_id + + @classmethod + def resource_cleanup(cls): + cls.delete_role_to_admin() + super(TestTempestImsNegative, cls).resource_cleanup() diff --git a/ranger_tempest_plugin/tests/api/test_region_groups.py b/ranger_tempest_plugin/tests/api/test_region_groups.py new file mode 100755 index 0000000..8b1adc2 --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_region_groups.py @@ -0,0 +1,87 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin import data_utils as orm_data_utils +from ranger_tempest_plugin.tests.api import base + +from tempest import config + +from tempest.lib import decorators + +from tempest.lib.common.utils import data_utils + +CONF = config.CONF + + +class TestTempestRegionGroup(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestRegionGroup, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestRegionGroup, cls).setup_clients() + cls.client = cls.os_primary.rms_client + + @classmethod + def resource_setup(cls): + cls.setup_ids = [] + cls.group_ids = [] + # create standard region + _, cls.region_1 = cls.client.create_region(data_utils.rand_name()) + cls.setup_ids.append(cls.region_1['id']) + # create region sharing region_1 properties + _, cls.region_2 = cls.client.create_region(data_utils.rand_name()) + cls.setup_ids.append(cls.region_2['id']) + + _, cls.group_1 = cls.client.create_region_group( + **orm_data_utils.rand_region_group([cls.setup_ids[0]]) + ) + cls.group_ids.append(cls.group_1['group']['id']) + _, cls.group_2 = cls.client.create_region_group( + **orm_data_utils.rand_region_group(cls.setup_ids) + ) + cls.group_ids.append(cls.group_2['group']['id']) + + super(TestTempestRegionGroup, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + for region_id in cls.setup_ids: + cls.client.delete_region(region_id) + for group_id in cls.group_ids: + cls.client.delete_region_group(group_id) + super(TestTempestRegionGroup, cls).resource_cleanup() + + @decorators.idempotent_id('0d377eb2-754d-49c1-9a4f-c7019dfe80ca') + def test_update_group(self): + id = self.group_ids[-1] + group = orm_data_utils.rand_region_group(self.setup_ids, id) + _, body = self.client.update_region_group(id, **group) + self.assertExpected(group, body['group'], ['regions']) + + @decorators.idempotent_id('b946c6c4-d601-42b9-befd-ba40992a3c53') + def test_list_groups(self): + _, body = self.client.list_region_groups() + groups = [x['id'] for x in body['groups']] + self.assertIn(self.group_1['group']['id'], groups) + self.assertIn(self.group_2['group']['id'], groups) + + @decorators.idempotent_id('9a37d966-4416-4ff3-8f3b-6847810662d7') + def test_get_group(self): + id = self.group_1['group']['id'] + _, body = self.client.get_region_group(id) + self.assertExpected(self.group_1['group'], body, ['links']) diff --git a/ranger_tempest_plugin/tests/api/test_regiongroups_negative.py b/ranger_tempest_plugin/tests/api/test_regiongroups_negative.py new file mode 100755 index 0000000..f5779ce --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_regiongroups_negative.py @@ -0,0 +1,42 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF + + +class TestTempestRegGroupNegative(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestRegGroupNegative, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestRegGroupNegative, cls).setup_clients() + cls.client = cls.rmsclient + + @classmethod + def resource_setup(cls): + cls.set_role_to_admin() + super(TestTempestRegGroupNegative, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls.delete_role_to_admin() + super(TestTempestRegGroupNegative, cls).resource_cleanup() diff --git a/ranger_tempest_plugin/tests/api/test_regions.py b/ranger_tempest_plugin/tests/api/test_regions.py new file mode 100755 index 0000000..bb255ab --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_regions.py @@ -0,0 +1,177 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin import data_utils as orm_data_utils +from ranger_tempest_plugin.tests.api import rms_base + +from tempest import config + +from tempest.lib import decorators +from tempest.lib import exceptions + +from tempest.lib.common.utils import data_utils + +CONF = config.CONF + + +class TestTempestRegion(rms_base.RmsBaseOrmTest): + + @classmethod + def resource_setup(cls): + cls.setup_ids = [] + # create standard region + _, cls.region_1 = cls.client.create_region(data_utils.rand_name()) + cls.setup_ids.append(cls.region_1['id']) + # create region sharing region_1 properties + _, cls.region_2 = cls.client.create_region(data_utils.rand_name()) + cls.setup_ids.append(cls.region_2['id']) + # create region with differing properties + _, cls.region_3 = cls.client.create_region( + data_utils.rand_name(), + **{'status': 'down', + 'rangerAgentVersion': '3.0', + 'OSVersion': 'mitaka', + 'CLLI': '123450', + 'address': {'country': 'Mexico', 'state': 'Sonora', + 'city': 'Nogales', 'street': '12 main', + 'zip': '84000'}, + 'metadata': {'meta1': ['val1']}, + 'designType': 'large'}) + cls.setup_ids.append(cls.region_3['id']) + + super(TestTempestRegion, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + for region_id in cls.setup_ids: + cls.client.delete_region(region_id) + super(TestTempestRegion, cls).resource_cleanup() + + @decorators.idempotent_id('829c7da0-2332-4f80-ad35-24306b67ed0e') + def test_create_and_delete_region(self): + # create new region for API test + region = orm_data_utils.rand_region() + _, region_body = self.client.create_region(region['id'], **region) + + test_region_name = region_body['name'] + test_region_id = region_body['id'] + _, body = self.client.get_region(test_region_name) + self.assertEqual(test_region_id, body['id']) + + # now delete the region + self.client.delete_region(test_region_id) + self.assertRaises(exceptions.NotFound, self.client.get_region, + test_region_id) + + @decorators.idempotent_id('eecedcb0-9c96-453d-bd72-71dba26fa1c5') + def test_list_region(self): + _, body = self.client.list_regions() + regions = [x['id'] for x in body['regions']] + self.assertIn(self.region_1['id'], regions) + + @decorators.idempotent_id('0164e040-7775-4837-9ac2-aaa0f71cdfca') + def test_list_region_v1(self): + _, body = self.client.list_regions_v1() + regions = [x['id'] for x in body] + self.assertIn(self.region_1['id'], regions) + + @decorators.idempotent_id('e6c6fdfe-5fa2-45f5-8bd8-fb9cf79e99fc') + def test_list_region_with_name(self): + filter = {'regionname': self.region_1['name']} + self._list_regions_with_filter(filter, 'name') + + @decorators.idempotent_id('b3310e32-0c31-4d37-9789-cc3c4639dabe') + def test_list_region_with_osversion(self): + filter = {'osversion': self.region_1['OSVersion']} + self._list_regions_with_filter(filter, 'OSVersion') + + @decorators.idempotent_id('0b2d3e79-c14a-4527-94b0-04eeae053a80') + def test_list_region_with_status(self): + filter = {'status': self.region_1['status']} + self._list_regions_with_filter(filter, 'status') + + @decorators.idempotent_id('871be582-ecaa-4a46-a403-4d6b5e59d7de') + def test_list_region_with_ranger_version(self): + filter = {'ranger_agent_version': self.region_1['rangerAgentVersion']} + self._list_regions_with_filter(filter, 'rangerAgentVersion') + + @decorators.idempotent_id('ac18be48-c787-4a65-913f-a0b0a80fbd1d') + def test_list_region_with_clli(self): + filter = {'clli': self.region_1['CLLI']} + self._list_regions_with_filter(filter, 'CLLI') + + @decorators.idempotent_id('f2b2361d-ce71-43a8-9f01-acb529835880') + def test_list_region_with_metadata(self): + filter = {'metadata': self.region_1['metadata'].keys()[0]} + self._list_regions_with_filter(filter, 'metadata') + + @decorators.idempotent_id('4533b31a-115d-466d-bf75-8ac24338c1a5') + def test_list_region_with_address(self): + filter = { + 'country': self.region_1['address']['country'], + 'city': self.region_1['address']['city'], + 'street': self.region_1['address']['street'], + 'zip': self.region_1['address']['zip'] + } + self._list_regions_with_filter(filter, 'address') + + @decorators.idempotent_id('726b8215-af10-4385-83c7-32b51502dff1') + def test_list_region_with_type(self): + filter = {'type': self.region_1['designType']} + self._list_regions_with_filter(filter, 'designType') + + @decorators.idempotent_id('4875ea70-a5a1-4b46-b752-246221670d26') + def test_list_region_with_vlcp(self): + filter = {'vlcp_name': self.region_1['vlcpName']} + self._list_regions_with_filter(filter, 'vlcpName') + + @decorators.idempotent_id('358f3cbc-4ae5-4b43-be36-6df55eae8fd9') + def test_get_region(self): + _, body = self.client.get_region(self.region_1['id']) + self.assertExpected(self.region_1, body, []) + + @decorators.idempotent_id('cefb952f-7777-4878-87d2-d78ac345f0d2') + def test_get_region_metadata(self): + _, body = self.client.get_region_metadata(self.region_1['id']) + self.assertExpected(self.region_1['metadata'], body['metadata'], []) + + @decorators.idempotent_id('b2c3baf5-22af-4bf9-bcad-b6a1a74e82d9') + def test_update_region(self): + id = self.setup_ids[-1] + region = orm_data_utils.rand_region(id) + _, body = self.client.update_region(id, **region) + self.assertExpected(region, body, []) + + @decorators.idempotent_id('0d5644d8-92bc-497c-8fc5-b57417d86e6d') + def test_update_region_status(self): + status = {} + status['status'] = orm_data_utils.rand_region_status( + [self.region_1['status']]) + _, body = self.client.update_region_status(self.region_1['id'], status) + self.assertExpected(status, body, ['links']) + + @decorators.idempotent_id('5c1a2624-6abe-49e7-82c8-30e8df1377d0') + def test_update_region_metadata(self): + metadata = {} + metadata['metadata'] = orm_data_utils.rand_region_metadata() + _, body = self.client.update_region_metadata(self.region_1['id'], + metadata) + self.assertExpected(metadata, body, []) + + def _list_regions_with_filter(self, filter, key): + _, body = self.client.list_regions(filter) + regions = [x for x in body['regions']] + self.assertTrue( + all([region[key] == self.region_1[key] for region in regions])) diff --git a/ranger_tempest_plugin/tests/api/test_regions_negative.py b/ranger_tempest_plugin/tests/api/test_regions_negative.py new file mode 100755 index 0000000..ee3ce2e --- /dev/null +++ b/ranger_tempest_plugin/tests/api/test_regions_negative.py @@ -0,0 +1,42 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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 ranger_tempest_plugin.tests.api import base + +from tempest import config + +CONF = config.CONF + + +class TestTempestRegionsNegative(base.BaseOrmTest): + + @classmethod + def setup_credentials(cls): + super(TestTempestRegionsNegative, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(TestTempestRegionsNegative, cls).setup_clients() + cls.client = cls.rmsclient + + @classmethod + def resource_setup(cls): + cls.set_role_to_admin() + super(TestTempestRegionsNegative, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls.delete_role_to_admin() + super(TestTempestRegionsNegative, cls).resource_cleanup() diff --git a/ranger_tempest_plugin/tests/scenario/__init__.py b/ranger_tempest_plugin/tests/scenario/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/setup.cfg b/setup.cfg new file mode 100755 index 0000000..84363fa --- /dev/null +++ b/setup.cfg @@ -0,0 +1,34 @@ +[metadata] +name = ranger_tempest_plugin +version = 0.0.1 +summary = Basic Tempest plugin for Ranger with a mos tempest smoke test case +description-file = +author = +author-email = +home-page = +classifier = + Development Status :: 4 - Beta + Environment :: Console + Environment :: OpenStack + Intended Audience :: OpenStack Development Team + Intended Audience :: OpenStack QA Team + Intended Audience :: Information Technology + Operating System :: Linux + Natural Language :: English + Topic :: Software Development :: Quality Assurance + Topic :: Software Development :: Testing + Programming Language :: Python +keywords = + Plugin + Tempest + OpenStack + +[files] +packages = ranger_tempest_plugin + +[entry_points] +tempest.test_plugins = + ranger-tempest-plugin = ranger_tempest_plugin.plugin:RangerPlugin + +[pbr] +warnerrors = true diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..238887d --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2012 OpenStack Foundation +# All Rights Reserved. +# +# 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 setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=2.0.0'], + pbr=True) diff --git a/tempest_setup/.stestr.conf b/tempest_setup/.stestr.conf new file mode 100644 index 0000000..23070af --- /dev/null +++ b/tempest_setup/.stestr.conf @@ -0,0 +1,6 @@ +[DEFAULT] +test_path=../ranger/ranger-tempest-plugin/ranger_tempest_plugin/tests/ +top_dir=../ranger/ranger-tempest-plugin/ +test_command=${PYTHON:-python} -m subunit.run discover orm $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/tempest_setup/accounts.yaml b/tempest_setup/accounts.yaml new file mode 100644 index 0000000..c3de52c --- /dev/null +++ b/tempest_setup/accounts.yaml @@ -0,0 +1,19 @@ +- username: 'm07057' + tenant_name: 'tempest_m07057' + password: 'devstack' + roles: + - 'admin' + +- username: 'm01690' + tenant_name: 'tempest_m01690' + password: 'devstack' + roles: + - 'ResellerAdmin' + - 'Member' + +- username: 'm01691' + tenant_name: 'tempest_m01691' + password: 'devstack' + roles: + - 'ResellerAdmin' + - 'Member' diff --git a/tempest_setup/create_tenant.sh b/tempest_setup/create_tenant.sh new file mode 100644 index 0000000..45619b9 --- /dev/null +++ b/tempest_setup/create_tenant.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +source devstack/openrc admin admin + +openstack project create 'tempest_m07057' +openstack project create 'tempest_m01690' +openstack project create 'tempest_m01691' + +openstack user create 'm07057' --project 'tempest_m07057' --password devstack +openstack user create 'm01690' --project 'tempest_m01690' --password devstack +openstack user create 'm01691' --project 'tempest_m01691' --password devstack + +openstack role add --project 'tempest_m07057' --user m07057 admin + +openstack role add --project 'tempest_m01690' --user m01690 ResellerAdmin +openstack role add --project 'tempest_m01690' --user m01690 Member + +openstack role add --project 'tempest_m01691' --user m01691 ResellerAdmin +openstack role add --project 'tempest_m01691' --user m01691 Member + +openstack role add --project 'tempest_m07057' --user admin admin +openstack role add --project 'tempest_m01690' --user admin admin +openstack role add --project 'tempest_m01691' --user admin admin \ No newline at end of file diff --git a/tempest_setup/ranger-tempest.sh b/tempest_setup/ranger-tempest.sh new file mode 100755 index 0000000..2088a08 --- /dev/null +++ b/tempest_setup/ranger-tempest.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Shell script to move to tempest, copy tempest.conf, and +# initialize tempest tests using stestr +# It should be noted that this script can not be ran out of +# the box and does require configuration before running. + +function execute_tests { + # move to Tempest directory + cd ${TEMPEST_DIRECTORY} + + # check for necessary folders, make them if not found + if [[ ! -d tempest_lock ]]; then + mkdir tempest_lock + fi + if [[ ! -d images ]]; then + mkdir images + fi + if [[ ! -d .stestr ]]; then + stestr init + fi + + # sets RANGER_DIRECTORY to relevant subdirectory and copies files for tests + RANGER_DIRECTORY=${RANGER_DIRECTORY}/ranger-tempest-plugin/tempest_setup + + # check for necessary files, copy them from ranger if not found + if [[ ! -e ./.stestr.conf ]]; then + cp ${RANGER_DIRECTORY}/.stestr.conf ./ + fi + if [[ ! -e etc/tempest.conf ]]; then + cp ${RANGER_DIRECTORY}/tempest.conf etc/ + fi + if [[ ! -e etc/create_tenant.sh ]]; then + cp ${RANGER_DIRECTORY}/create_tenant.sh etc/ + fi + if [[ ! -e etc/accounts.yaml ]]; then + cp ${RANGER_DIRECTORY}/accounts.yaml etc/ + fi + + # runs tests using stestr and regex, ex: ranger_tempest_plugin.tests.api.test_regions + stestr run --concurrency ${CONCURRENCY} --log-file /var/log/tempest/tempest_run.log ${TEST_REGEX} +} + +usage() +{ +cat << EOF +usage: ./ranger-tempest.sh -t TEMPEST_DIRECTORY -c CONCURRENCY -r RANGER_DIRECTORY -f TEST_REGEX + +This script automates a few steps necessary to run Tempest against Ranger + +OPTIONS: + -h Show this message + -t The Tempest Folder fully-formed path + -c Concurrency + -r The location of your Ranger folder + -f The regex representing the tests that will be ran +EOF +} + +TEMPEST_DIRECTORY= +CONCURRENCY=1 +TEST_REGEX= +RANGER_DIRECTORY= +while getopts "ht:c:f:r:" OPTION +do + case $OPTION in + h) + usage + exit 1 + ;; + t) + TEMPEST_DIRECTORY=$OPTARG + ;; + c) + CONCURRENCY=$OPTARG + ;; + f) + TEST_REGEX=$OPTARG + ;; + r) + RANGER_DIRECTORY=$OPTARG + ;; + ?) + usage + exit + ;; + esac +done + +if [[ -z $TEMPEST_DIRECTORY ]]; then + echo "The script requires the location of the Tempest folder" + usage + exit 1 +#elif [[ -z $TEST_REGEX ]]; then +# echo "The script expects a regex of tests to run" +# usage +# exit 1 +elif [[ -z $RANGER_DIRECTORY ]]; then + echo "This script requires the location of the Ranger folder" + usage + exit 1 +else + execute_tests +fi + diff --git a/tempest_setup/tempest.conf b/tempest_setup/tempest.conf new file mode 100644 index 0000000..ee75b98 --- /dev/null +++ b/tempest_setup/tempest.conf @@ -0,0 +1,118 @@ +[DEFAULT] +debug = true +log_file = tempest.log +log_dir = /var/log/tempest + +[auth] +test_accounts_file = /opt/stack/tempest/etc/accounts.yaml +use_dynamic_credentials = true +tempest_roles = admin +admin_username = admin +admin_project_name = admin +admin_password = nomoresecret +admin_domain_name = Default + +[compute] +# uncomment image_ref and image_ref_alt and set their values accordingly +# image_ref = +# image_ref_alt = +flavor_ref = 1 +flavor_ref_alt = 84 +fixed_network_name = public + +[compute-feature-enabled] +change_password = false +console_output = true +resize = true +vnc_console = true +enable_instance_password = true +attach_encrypted_volume = false +scheduler_available_filters = all + +[identity] +disable_ssl_certificate_validation = false +ca_certificates_file = /etc/ssl/certs/ca-certificates.crt +uri = http://192.168.56.130:5000/v2.0 +uri_v3 = http://192.168.56.130:5000/v3 +auth_version = v3 +region = local +admin_role = admin + +[identity-feature-enabled] +api_v2 = false +api_v2_admin = false +api_v3 = true +api_extensions = all + +[image-feature-enabled] +deactivate_image = true + +[network] +# uncomment public_network_id and set it to 'openstack network list | grep public' ID value +public_network_id = 5a9ae5bf-0570-4a44-b8fa-5ba2fb60d850 +floating_network_name = public + +[network-feature-enabled] +ipv6 = true +api_extensions = contrail,security-group,ipam,port-security,binding,provider,agent,quotas,route-table,extra_lbaas_opts,external-net,policy,router,allowed-address-pairs,extra_dhcp_opt,service-interface,lbaas +ipv6_subnet_attributes = true + +[object-storage] +operator_role = Member +reseller_admin_role = ResellerAdmin + +[orchestration] +instance_type = 1 + +[oslo_concurrency] +lock_path = tempest_lock + +[scenario] +img_dir = images +img_file = cirros-0.3.4-x86_64-disk.img + +[service_available] +heat-cfn = true +cinder = true +swift = true +cinderv2 = true +heat = true +novav3 = true +trove = true +designate = true +ceilometer = true +murano = true +keystone = true +s3 = true +glance = true +neutron = true +nova = true +nova_ec2 = true +ranger = true +contrail = true +mistral = true + +[validation] +run_validation = true +connect_method = floating +image_ssh_user = cirros +# set image_ssh_password as appropriate +image_ssh_password = password + +[volume] +backend_names = SOLIDFIRE +storage_protocol = iSCSI + +[volume-feature-enabled] +multi_backend = false + +[ranger] +RANGER_RMS_BASE_URL='http://192.168.56.127:8080' +RANGER_CMS_BASE_URL='http://192.168.56.127:7080' +RANGER_FMS_BASE_URL='http://192.168.56.127:8082' +RANGER_IMS_BASE_URL='http://192.168.56.127:8084' +image_url=http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-i386/current/images/netboot/mini.iso +# verify=True +auth_enabled=True +catalog_type = ranger +flavor_series = p1 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..c6da58f --- /dev/null +++ b/tox.ini @@ -0,0 +1,71 @@ +[tox] +minversion = 1.6 +envlist = py27,pep8,pylint +skipdist = True + + +[testenv] +usedevelop = True +install_command = pip install {opts} {packages} +setenv = + VIRTUAL_ENV={envdir} +commands = + find . -type f -name "*.pyc" -delete +whitelist_externals = + find + + +[testenv:venv] +commands = {posargs} + + +#Linters +[testenv:pep8] +sitepackages = True +basepython = python +deps = + flake8>=2.6.2, <3.4.0 + flake8-import-order>=0.9, <0.13 +commands = + bash changed_python_files.sh {toxinidir} "flake" {posargs} +whitelist_externals = + bash + +# For use in development environment +[testenv:pep8dev] +sitepackages = True +basepython = python +skip_install = true +deps = + {[testenv:pep8]deps} +commands = + {[testenv:pep8]commands} dev +whitelist_externals = + bash + + +# At this time we are only checking for C0103(invalid-name) which in the near +# future will be expanded upon +[testenv:pylint] +sitepackages = True +basepython = python +deps = + pylint==1.7.2 +commands = + bash changed_python_files.sh {toxinidir} "pylint" {posargs} +whitelist_externals = + bash + + +# For use in development environment +[testenv:pylintdev] +sitepackages = True +basepython = python +skip_install = true +deps = + {[testenv:pylint]deps} +commands = + {[testenv:pylint]commands} dev +whitelist_externals = + bash +