1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
|
"""Unit tests for utils"""
from unittest import TestCase
import pytest
from unittest.mock import Mock
from elastic_transport import ApiResponseMeta
from elasticsearch8 import Elasticsearch
from elasticsearch8.exceptions import AuthenticationException, NotFoundError
from curator.exceptions import (
ConfigurationError, FailedExecution, MissingArgument, RepositoryException,
SearchableSnapshotException)
from curator.helpers.testers import (
has_lifecycle_name, is_idx_partial, repository_exists, rollable_alias, snapshot_running,
validate_filters, verify_client_object, verify_repository)
FAKE_FAIL = Exception('Simulated Failure')
class TestRepositoryExists(TestCase):
"""TestRepositoryExists
Test helpers.testers.repository_exists functionality.
"""
def test_missing_arg(self):
"""test_missing_arg
Should raise an exception if the repository isn't passed as an arg
"""
client = Mock()
with pytest.raises(MissingArgument, match=r'No value for "repository" provided'):
repository_exists(client)
def test_repository_in_results(self):
"""test_repository_in_results
Should return ``True`` if the passed repository exists
"""
client = Mock()
client.snapshot.get_repository.return_value = {'repo':{'foo':'bar'}}
assert repository_exists(client, repository="repo")
def test_repo_not_in_results(self):
"""test_repo_not_in_results
Should return ``False`` if the passed repository does not exist
"""
client = Mock()
client.snapshot.get_repository.return_value = {'not_your_repo':{'foo':'bar'}}
assert not repository_exists(client, repository="repo")
class TestRollableAlias(TestCase):
"""TestRollableAlias
Test helpers.testers.rollable_alias functionality.
"""
def test_return_false_if_no_alias(self):
"""test_return_false_if_no_alias
Should return ``False`` with a simulated Exception being raised
"""
err = 'simulated error'
client = Mock()
client.info.return_value = {'version': {'number': '8.3.3'} }
client.indices.get_alias.return_value = {}
client.indices.get_alias.side_effect = NotFoundError(404, err, err)
assert not rollable_alias(client, 'foo')
def test_return_false_too_many_indices(self):
"""test_return_false_too_many_indices
Should return ``False`` if alias associated with too many indices
"""
retval = {'index-a': {'aliases': {'foo': {}}}, 'index-b': {'aliases': {'foo': {}}}}
client = Mock()
client.info.return_value = {'version': {'number': '8.3.3'} }
client.indices.get_alias.return_value = retval
assert not rollable_alias(client, 'foo')
def test_return_false_non_numeric(self):
"""test_return_false_non_numeric
Should return ``False`` if index name doesn't end in rollable digits
"""
retval = {'index-a': {'aliases': {'foo': {}}}}
client = Mock()
client.info.return_value = {'version': {'number': '8.3.3'} }
client.indices.get_alias.return_value = retval
assert not rollable_alias(client, 'foo')
def test_return_true_two_digits(self):
"""test_return_true_two_digits
Should return ``True`` if the index ends in rollable digits
"""
retval = {'index-00001': {'aliases': {'foo': {}}}}
client = Mock()
client.info.return_value = {'version': {'number': '8.3.3'} }
client.indices.get_alias.return_value = retval
assert rollable_alias(client, 'foo')
def test_return_true_hyphenated(self):
"""test_return_true_hyphenated
Should return ``True`` if the index has a rollable, hyphenated name
"""
retval = {'index-2017.03.07-1': {'aliases': {'foo': {}}}}
client = Mock()
client.info.return_value = {'version': {'number': '8.3.3'} }
client.indices.get_alias.return_value = retval
assert rollable_alias(client, 'foo')
class TestSnapshotRunning(TestCase):
"""TestSnapshotRunning
Test helpers.testers.snapshot_running functionality
"""
# :pylint disable=line-too-long
def test_true(self):
"""test_true
Should return ``True`` when a snapshot is in progress/running.
"""
client = Mock()
client.snapshot.status.return_value = {'snapshots': ['running']}
# self.assertTrue(snapshot_running(client))
assert snapshot_running(client)
def test_false(self):
"""test_False
Should return ``False`` when a snapshot is not in progress/running.
"""
client = Mock()
client.snapshot.status.return_value = {'snapshots': []}
# self.assertFalse(snapshot_running(client))
assert not snapshot_running(client)
def test_raises_exception(self):
"""test_raises_exception
Should raise a ``FailedExecution`` exception when an exception happens upstream
"""
client = Mock()
client.snapshot.status.return_value = {'snapshots': []}
client.snapshot.status.side_effect = FAKE_FAIL
# self.assertRaises(FailedExecution, snapshot_running, client)
with pytest.raises(FailedExecution, match=r'Rerun with loglevel DEBUG'):
snapshot_running(client)
class TestValidateFilters(TestCase):
"""TestValidateFilters
Test helpers.testers.validate_filters functionality.
"""
def test_snapshot_with_index_filter(self):
"""test_snapshot_with_index_filter
Should raise ConfigurationError with improper filter for filtertype
In this case, an index filtertype (``kibana``) for the ``delete_snapshots`` filter
"""
with pytest.raises(ConfigurationError, match=r'filtertype is not compatible with action'):
validate_filters('delete_snapshots', [{'filtertype': 'kibana'}])
def test_index_with_snapshot_filter(self):
"""test_index_with_snapshot_filter
Should raise ConfigurationError with improper filter for filtertype
In this case, a snapshot filtertype (``state``) for the ``delete_indices`` filter
"""
with pytest.raises(ConfigurationError, match=r'filtertype is not compatible with action'):
validate_filters('delete_indices', [{'filtertype': 'state', 'state': 'SUCCESS'}])
class TestVerifyClientObject(TestCase):
"""TestVerifyClientObject
Test helpers.testers.verify_client_object functionality.
"""
def test_is_client_object(self):
"""test_is_client_object
Should return a ``None`` value for a valid client object.
"""
test = Elasticsearch(hosts=["http://127.0.0.1:9200"])
assert None is verify_client_object(test)
def test_is_not_client_object(self):
"""test_is_not_client_object
Should raise a ``TypeError`` exception with an invalid client object.
"""
test = 'not a client object'
with pytest.raises(TypeError, match=r'Not a valid client object'):
verify_client_object(test)
class TestVerifyRepository(TestCase):
"""TestVerifyRepository
Test helpers.testers.verify_repository functionality
"""
VERIFIED_NODES = {'nodes': {'nodeid1': {'name': 'node1'}, 'nodeid2': {'name': 'node2'}}}
REPO_NAME = 'repo_name'
def test_passing(self):
"""test_passing
Should return ``None`` and raise no Exception on success
"""
client = Mock()
client.snapshot.verify_repository.return_value = self.VERIFIED_NODES
assert None is verify_repository(client, repository=self.REPO_NAME)
def test_raises_404(self):
"""test_raises_404
Should raise ``RepositoryException`` when a 404 ``TransportError`` raises first
"""
client = Mock()
client.snapshot.verify_repository.return_value = self.VERIFIED_NODES
# 5 positional args for meta: status, http_version, headers, duration, node
meta = ApiResponseMeta(404, '1.1', {}, 0.01, None)
body = f'[{self.REPO_NAME}] missing'
msg = 'repository_missing_exception'
# 3 positional args for NotFoundError: message, meta, body
effect = NotFoundError(msg, meta, body)
client.snapshot.verify_repository.side_effect = effect
with pytest.raises(RepositoryException, match=r'Repository "repo_name" not found'):
verify_repository(client, repository=self.REPO_NAME)
def test_raises_401(self):
"""test_raises_401
Should raise ``RepositoryException`` when a 401 AuthenticationException raises first
"""
client = Mock()
client.snapshot.verify_repository.return_value = self.VERIFIED_NODES
# 5 positional args for meta: status, http_version, headers, duration, node
meta = ApiResponseMeta(401, '1.1', {}, 0.01, None)
body = 'No authentication'
msg = 'authentication error'
# 3 positional args for NotFoundError: message, meta, body
effect = AuthenticationException(msg, meta, body)
client.snapshot.verify_repository.side_effect = effect
with pytest.raises(RepositoryException, match=r'Got a 401 response from Elasticsearch'):
verify_repository(client, repository=self.REPO_NAME)
def test_raises_other(self):
"""test_raises_other
Should raise ``RepositoryException`` when any other Exception raises first
"""
client = Mock()
client.snapshot.verify_repository.return_value = self.VERIFIED_NODES
client.snapshot.verify_repository.side_effect = FAKE_FAIL
with pytest.raises(RepositoryException, match=r'Failed to verify'):
verify_repository(client, repository=self.REPO_NAME)
class TestHasLifecycleName(TestCase):
"""TestHasLifecycleName
Test helpers.testers.has_lifecycle_name functionality
"""
def test_has_lifecycle_name(self):
"""test_has_lifecycle_name"""
testval = {'lifecycle': {'name': 'ilm_policy'}}
assert has_lifecycle_name(testval)
def test_has_no_lifecycle_name(self):
"""test_has_no_lifecycle_name"""
testval = {'lifecycle': {'nothere': 'nope'}}
assert not has_lifecycle_name(testval)
class TestIsIdxPartial(TestCase):
"""TestIsIdxPartial
Test helpers.testers.is_idx_partial functionality
"""
def test_is_idx_partial(self):
"""test_is_idx_partial"""
testval = {'store': {'snapshot': {'partial': True}}}
assert is_idx_partial(testval)
def test_is_idx_partial_false1(self):
"""test_is_idx_partial_false1"""
testval = {'store': {'snapshot': {'partial': False}}}
assert not is_idx_partial(testval)
def test_is_idx_partial_false2(self):
"""test_is_idx_partial_false2"""
testval = {'store': {'snapshot': {'nothere': 'nope'}}}
assert not is_idx_partial(testval)
def test_is_idx_partial_raises1(self):
"""test_is_idx_partial_raises1"""
testval = {'store': {'nothere': 'nope'}}
with pytest.raises(SearchableSnapshotException, match='not a mounted searchable snapshot'):
is_idx_partial(testval)
def test_is_idx_partial_raises2(self):
"""test_is_idx_partial_raises2"""
testval = {'nothere': 'nope'}
with pytest.raises(SearchableSnapshotException, match='not a mounted searchable snapshot'):
is_idx_partial(testval)
|