Skip to content

Commit

Permalink
Restruct rally.plugins.common module
Browse files Browse the repository at this point in the history
Change-Id: I9df675afdf0a6b76916fba7ed9c3712136ad4780
  • Loading branch information
andreykurilin committed Mar 21, 2020
1 parent 208b2b6 commit 4a466f2
Show file tree
Hide file tree
Showing 106 changed files with 3,330 additions and 2,712 deletions.
26 changes: 25 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,38 @@ Changed

* *path_or_url* plugin follows redirects while validating urls now.

* *rally task sla-check` fails if there is no data.
* *rally task sla-check* fails if there is no data.

Deprecated
~~~~~~~~~~

* Module *rally.common.sshutils* is deprecated. Use *rally.utils.sshutils*
instead.

* All modules from *rally.plugins.common.contexts* are deprecated. Use
*rally.plugins.task.contexts* instead.

* All modules from *rally.plugins.common.exporters* are deprecated. Use
*rally.plugins.task.exporters* instead.

* Module *rally.plugins.common.hook.sys_call* is deprecated. Use
*rally.plugins.task.hooks.sys_call* instead.

* All modules from *rally.plugins.common.hook.triggers* are deprecated. Use
*rally.plugins.task.hook_triggers* instead.

* All modules from *rally.plugins.common.runners* are deprecated. Use
*rally.plugins.task.runners* instead.

* All modules from *rally.plugins.common.scenarios* are deprecated. Use
*rally.plugins.task.scenarios* instead.

* All modules from *rally.plugins.common.sla* are deprecated. Use
*rally.plugins.task.sla* instead.

* All modules from *rally.plugins.common.verification* are deprecated. Use
*rally.plugins.verification* instead.

Removed
~~~~~~~

Expand Down
9 changes: 9 additions & 0 deletions rally/common/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import functools
import traceback
import warnings

from oslo_log import handlers
from oslo_log import log as oslogging
Expand Down Expand Up @@ -331,5 +332,13 @@ def wrapper(*args, **kwargs):
return decorator


def log_deprecated_module(target, new_module, release):
warnings.warn(
f"Module `{target}` moved to `{new_module}` since Rally v{release}. "
f"The import from old place is deprecated and may be removed in "
f"further releases."
)


def is_debug():
return CONF.debug or CONF.rally_debug
10 changes: 6 additions & 4 deletions rally/common/sshutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.

from rally.utils.sshutils import * # noqa
from rally.utils.sshutils import * # noqa: F401,F403
from rally.utils import sshutils as _new

# import it as last item to be sure that we use the right module
from rally.common import logging

logging.getLogger(__name__).warning(
f"Module {__name__} moved to rally.utils.sshutils. "
f"Please correct your import."

logging.log_deprecated_module(
target=__name__, new_module=_new.__name__, release="3.0.0"
)
10 changes: 9 additions & 1 deletion rally/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ def load():

opts.register()

discover.import_modules_from_package("rally.plugins.common")
# NOTE(andreykurilin): `rally.plugins.common` includes deprecated
# modules. As soon as they will be removed the direct import of
# validators should be replaced by
#
# discover.import_modules_from_package("rally.plugins.common")
from rally.plugins.common import validators # noqa: F401

discover.import_modules_from_package("rally.plugins.task")
discover.import_modules_from_package("rally.plugins.verification")

packages = discover.find_packages_by_entry_point()
for package in packages:
Expand Down
147 changes: 6 additions & 141 deletions rally/plugins/common/exporters/elastic/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,148 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.

import copy

import requests
from rally.plugins.task.exporters.elastic.client import * # noqa: F401,F403
from rally.plugins.task.exporters.elastic import client as _new

# import it as last item to be sure that we use the right module
from rally.common import logging
from rally import exceptions

LOG = logging.getLogger(__name__)


class ElasticSearchClient(object):
"""The helper class for communication with ElasticSearch 2.*, 5.*, 6.*"""

# a number of documents to push to the cluster at once.
CHUNK_LENGTH = 10000

def __init__(self, url):
self._url = url.rstrip("/") if url else "http://localhost:9200"
self._version = None

@staticmethod
def _check_response(resp, action=None):
if resp.status_code in (200, 201):
return
# it is an error. let's try to find the reason
reason = None
try:
data = resp.json()
except ValueError:
# it is ok
pass
else:
if "error" in data:
if isinstance(data["error"], dict):
reason = data["error"].get("reason", "")
else:
reason = data["error"]
reason = reason or resp.text or "n/a"
action = action or "connect to"
raise exceptions.RallyException(
"[HTTP %s] Failed to %s ElasticSearch cluster: %s" %
(resp.status_code, action, reason))

def version(self):
"""Get version of the ElasticSearch cluster."""
if self._version is None:
self.info()
return self._version

def info(self):
"""Retrieve info about the ElasticSearch cluster."""
resp = requests.get(self._url)
self._check_response(resp)
err_msg = "Failed to retrieve info about the ElasticSearch cluster: %s"
try:
data = resp.json()
except ValueError:
LOG.debug("Return data from %s: %s" % (self._url, resp.text))
raise exceptions.RallyException(
err_msg % "The return data doesn't look like a json.")
version = data.get("version", {}).get("number")
if not version:
LOG.debug("Return data from %s: %s" % (self._url, resp.text))
raise exceptions.RallyException(
err_msg % "Failed to parse the received data.")
self._version = version
if self._version.startswith("2"):
data["version"]["build_date"] = data["version"].pop(
"build_timestamp")
return data

def push_documents(self, documents):
"""Push documents to the ElasticSearch cluster using bulk API.
:param documents: a list of documents to push
"""
LOG.debug("Pushing %s documents by chunks (up to %s documents at once)"
" to ElasticSearch." %
# dividing numbers by two, since each documents has 2 lines
# in `documents` (action and document itself).
(len(documents) / 2, self.CHUNK_LENGTH / 2))

for pos in range(0, len(documents), self.CHUNK_LENGTH):
data = "\n".join(documents[pos:pos + self.CHUNK_LENGTH]) + "\n"

raw_resp = requests.post(
self._url + "/_bulk", data=data,
headers={"Content-Type": "application/x-ndjson"}
)
self._check_response(raw_resp, action="push documents to")

LOG.debug("Successfully pushed %s documents." %
len(raw_resp.json()["items"]))

def list_indices(self):
"""List all indices."""
resp = requests.get(self._url + "/_cat/indices?v")
self._check_response(resp, "list the indices at")

return resp.text.rstrip().split(" ")

def create_index(self, name, doc_type, properties):
"""Create an index.
There are two very different ways to search strings. You can either
search whole values, that we often refer to as keyword search, or
individual tokens, that we usually refer to as full-text search.
In ElasticSearch 2.x `string` data type is used for these cases whereas
ElasticSearch 5.0 the `string` data type was replaced by two new types:
`keyword` and `text`. Since it is hard to predict the destiny of
`string` data type and support of 2 formats of input data, the
properties should be transmitted in ElasticSearch 5.x format.
"""
if self.version().startswith("2."):
properties = copy.deepcopy(properties)
for spec in properties.values():
if spec.get("type", None) == "text":
spec["type"] = "string"
elif spec.get("type", None) == "keyword":
spec["type"] = "string"
spec["index"] = "not_analyzed"

resp = requests.put(
self._url + "/%s" % name,
json={"mappings": {doc_type: {"properties": properties}}})
self._check_response(resp, "create index at")

def check_document(self, index, doc_id, doc_type="data"):
"""Check for the existence of a document.

:param index: The index of a document
:param doc_id: The ID of a document
:param doc_type: The type of a document (Defaults to data)
"""
resp = requests.head("%(url)s/%(index)s/%(type)s/%(id)s" %
{"url": self._url,
"index": index,
"type": doc_type,
"id": doc_id})
if resp.status_code == 200:
return True
elif resp.status_code == 404:
return False
else:
self._check_response(resp, "check the index at")
logging.log_deprecated_module(
target=__name__, new_module=_new.__name__, release="3.0.0"
)

0 comments on commit 4a466f2

Please sign in to comment.