Skip to content

Commit eca0b2d

Browse files
authored
Merge pull request #5893: Update pants-plugins/uses_services to support checking for redis
2 parents 8a92560 + a8fd0d3 commit eca0b2d

File tree

8 files changed

+323
-1
lines changed

8 files changed

+323
-1
lines changed

.github/workflows/test.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ jobs:
5454
- 5672:5672/tcp # AMQP standard port
5555
- 15672:15672/tcp # Management: HTTP, CLI
5656

57+
redis:
58+
# Docker Hub image
59+
image: redis
60+
# Set health checks to wait until redis has started
61+
options: >-
62+
--name "redis"
63+
--health-cmd "redis-cli ping"
64+
--health-interval 10s
65+
--health-timeout 5s
66+
--health-retries 5
67+
ports:
68+
- 6379:6379/tcp
69+
5770
env:
5871
COLUMNS: '120'
5972

CHANGELOG.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Added
1515
working on StackStorm, improve our security posture, and improve CI reliability thanks in part
1616
to pants' use of PEX lockfiles. This is not a user-facing addition.
1717
#5778 #5789 #5817 #5795 #5830 #5833 #5834 #5841 #5840 #5838 #5842 #5837 #5849 #5850
18-
#5846 #5853 #5848 #5847 #5858 #5857 #5860 #5868 #5871 #5864 #5874 #5884
18+
#5846 #5853 #5848 #5847 #5858 #5857 #5860 #5868 #5871 #5864 #5874 #5884 #5893
1919
Contributed by @cognifloyd
2020

2121
* Added a joint index to solve the problem of slow mongo queries for scheduled executions. #5805

contrib/runners/orquesta_runner/tests/integration/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ __defaults__(
55

66
python_tests(
77
name="tests",
8+
uses=["redis"],
89
)

pants-plugins/uses_services/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ python_tests(
1616
# overrides={
1717
# "mongo_rules_test.py": {"uses": ["mongo"]},
1818
# "rabbitmq_rules_test.py": {"uses": ["rabbitmq"]},
19+
# "redis_rules_test.py": {"uses": ["redis"]},
1920
# },
2021
)
+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
from dataclasses import dataclass
17+
from textwrap import dedent
18+
19+
from pants.backend.python.goals.pytest_runner import (
20+
PytestPluginSetupRequest,
21+
PytestPluginSetup,
22+
)
23+
from pants.backend.python.util_rules.pex import (
24+
PexRequest,
25+
PexRequirements,
26+
VenvPex,
27+
VenvPexProcess,
28+
rules as pex_rules,
29+
)
30+
from pants.engine.fs import CreateDigest, Digest, FileContent
31+
from pants.engine.rules import collect_rules, Get, MultiGet, rule
32+
from pants.engine.process import FallibleProcessResult, ProcessCacheScope
33+
from pants.engine.target import Target
34+
from pants.engine.unions import UnionRule
35+
from pants.util.logging import LogLevel
36+
37+
from uses_services.exceptions import ServiceMissingError, ServiceSpecificMessages
38+
from uses_services.platform_rules import Platform
39+
from uses_services.scripts.is_redis_running import (
40+
__file__ as is_redis_running_full_path,
41+
)
42+
from uses_services.target_types import UsesServicesField
43+
44+
45+
@dataclass(frozen=True)
46+
class UsesRedisRequest:
47+
"""One or more targets need a running redis service using these settings.
48+
49+
The coord_* attributes represent the coordination settings from st2.conf.
50+
In st2 code, they come from:
51+
oslo_config.cfg.CONF.coordination.url
52+
"""
53+
54+
# These config opts for integration tests are in:
55+
# conf/st2.dev.conf (copied to conf/st2.ci.conf)
56+
# TODO: for int tests: set url by either modifying st2.{dev,ci}.conf on the fly or via env vars.
57+
58+
# with our version of oslo.config (newer are slower) we can't directly override opts w/ environment variables.
59+
60+
coord_url: str = "redis://127.0.0.1:6379"
61+
62+
63+
@dataclass(frozen=True)
64+
class RedisIsRunning:
65+
pass
66+
67+
68+
class PytestUsesRedisRequest(PytestPluginSetupRequest):
69+
@classmethod
70+
def is_applicable(cls, target: Target) -> bool:
71+
if not target.has_field(UsesServicesField):
72+
return False
73+
uses = target.get(UsesServicesField).value
74+
return uses is not None and "redis" in uses
75+
76+
77+
@rule(
78+
desc="Ensure redis is running and accessible before running tests.",
79+
level=LogLevel.DEBUG,
80+
)
81+
async def redis_is_running_for_pytest(
82+
request: PytestUsesRedisRequest,
83+
) -> PytestPluginSetup:
84+
# this will raise an error if redis is not running
85+
_ = await Get(RedisIsRunning, UsesRedisRequest())
86+
87+
return PytestPluginSetup()
88+
89+
90+
@rule(
91+
desc="Test to see if redis is running and accessible.",
92+
level=LogLevel.DEBUG,
93+
)
94+
async def redis_is_running(
95+
request: UsesRedisRequest, platform: Platform
96+
) -> RedisIsRunning:
97+
script_path = "./is_redis_running.py"
98+
99+
# pants is already watching this directory as it is under a source root.
100+
# So, we don't need to double watch with PathGlobs, just open it.
101+
with open(is_redis_running_full_path, "rb") as script_file:
102+
script_contents = script_file.read()
103+
104+
script_digest, tooz_pex = await MultiGet(
105+
Get(Digest, CreateDigest([FileContent(script_path, script_contents)])),
106+
Get(
107+
VenvPex,
108+
PexRequest(
109+
output_filename="tooz.pex",
110+
internal_only=True,
111+
requirements=PexRequirements({"tooz", "redis"}),
112+
),
113+
),
114+
)
115+
116+
result = await Get(
117+
FallibleProcessResult,
118+
VenvPexProcess(
119+
tooz_pex,
120+
argv=(
121+
script_path,
122+
request.coord_url,
123+
),
124+
input_digest=script_digest,
125+
description="Checking to see if Redis is up and accessible.",
126+
# this can change from run to run, so don't cache results.
127+
cache_scope=ProcessCacheScope.PER_SESSION,
128+
level=LogLevel.DEBUG,
129+
),
130+
)
131+
is_running = result.exit_code == 0
132+
133+
if is_running:
134+
return RedisIsRunning()
135+
136+
# redis is not running, so raise an error with instructions.
137+
raise ServiceMissingError.generate(
138+
platform=platform,
139+
messages=ServiceSpecificMessages(
140+
service="redis",
141+
service_start_cmd_el_7="service redis start",
142+
service_start_cmd_el="systemctl start redis",
143+
not_installed_clause_el="this is one way to install it:",
144+
install_instructions_el=dedent(
145+
"""\
146+
sudo yum -y install redis
147+
# Don't forget to start redis.
148+
"""
149+
),
150+
service_start_cmd_deb="systemctl start redis",
151+
not_installed_clause_deb="this is one way to install it:",
152+
install_instructions_deb=dedent(
153+
"""\
154+
sudo apt-get install -y mongodb redis
155+
# Don't forget to start redis.
156+
"""
157+
),
158+
service_start_cmd_generic="systemctl start redis",
159+
),
160+
)
161+
162+
163+
def rules():
164+
return [
165+
*collect_rules(),
166+
UnionRule(PytestPluginSetupRequest, PytestUsesRedisRequest),
167+
*pex_rules(),
168+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
import pytest
17+
18+
from pants.engine.internals.scheduler import ExecutionError
19+
from pants.testutil.rule_runner import QueryRule, RuleRunner
20+
21+
from .data_fixtures import platform, platform_samples
22+
from .exceptions import ServiceMissingError
23+
from .redis_rules import (
24+
RedisIsRunning,
25+
UsesRedisRequest,
26+
rules as redis_rules,
27+
)
28+
from .platform_rules import Platform
29+
30+
31+
@pytest.fixture
32+
def rule_runner() -> RuleRunner:
33+
return RuleRunner(
34+
rules=[
35+
*redis_rules(),
36+
QueryRule(RedisIsRunning, (UsesRedisRequest, Platform)),
37+
],
38+
target_types=[],
39+
)
40+
41+
42+
def run_redis_is_running(
43+
rule_runner: RuleRunner,
44+
uses_redis_request: UsesRedisRequest,
45+
mock_platform: Platform,
46+
*,
47+
extra_args: list[str] | None = None,
48+
) -> RedisIsRunning:
49+
rule_runner.set_options(
50+
[
51+
"--backend-packages=uses_services",
52+
*(extra_args or ()),
53+
],
54+
env_inherit={"PATH", "PYENV_ROOT", "HOME"},
55+
)
56+
result = rule_runner.request(
57+
RedisIsRunning,
58+
[uses_redis_request, mock_platform],
59+
)
60+
return result
61+
62+
63+
# Warning this requires that redis be running
64+
def test_redis_is_running(rule_runner: RuleRunner) -> None:
65+
request = UsesRedisRequest()
66+
mock_platform = platform(os="TestMock")
67+
68+
# we are asserting that this does not raise an exception
69+
is_running = run_redis_is_running(rule_runner, request, mock_platform)
70+
assert is_running
71+
72+
73+
@pytest.mark.parametrize("mock_platform", platform_samples)
74+
def test_redis_not_running(rule_runner: RuleRunner, mock_platform: Platform) -> None:
75+
request = UsesRedisRequest(
76+
coord_url="redis://127.100.20.7:10", # 10 is an unassigned port, unlikely to be used
77+
)
78+
79+
with pytest.raises(ExecutionError) as exception_info:
80+
run_redis_is_running(rule_runner, request, mock_platform)
81+
82+
execution_error = exception_info.value
83+
assert len(execution_error.wrapped_exceptions) == 1
84+
85+
exc = execution_error.wrapped_exceptions[0]
86+
assert isinstance(exc, ServiceMissingError)
87+
88+
assert exc.service == "redis"
89+
assert "The redis service does not seem to be running" in str(exc)
90+
assert exc.instructions != ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
import sys
17+
18+
19+
def _is_redis_running(coord_url: str) -> bool:
20+
"""Connect to redis with connection logic that mirrors the st2 code.
21+
22+
In particular, this is based on:
23+
- st2common.services.coordination.coordinator_setup()
24+
25+
This should not import the st2 code as it should be self-contained.
26+
"""
27+
# late import so that __file__ can be imported in the pants plugin without these imports
28+
from tooz import ToozError, coordination
29+
30+
member_id = "pants-uses_services-redis"
31+
coordinator = coordination.get_coordinator(coord_url, member_id)
32+
try:
33+
coordinator.start(start_heart=False)
34+
except ToozError:
35+
return False
36+
return True
37+
38+
39+
if __name__ == "__main__":
40+
args = dict((k, v) for k, v in enumerate(sys.argv))
41+
42+
# unit tests do not use redis, they use use an in-memory coordinator: "zake://"
43+
# integration tests use this url with a conf file derived from conf/st2.dev.conf
44+
coord_url = args.get(1, "redis://127.0.0.1:6379")
45+
46+
is_running = _is_redis_running(coord_url)
47+
exit_code = 0 if is_running else 1
48+
sys.exit(exit_code)

st2tests/integration/orquesta/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ python_sources()
22

33
python_tests(
44
name="tests",
5+
uses=["redis"],
56
)

0 commit comments

Comments
 (0)