Skip to content

Commit

Permalink
test: agent relation integration (#12)
Browse files Browse the repository at this point in the history
* test: new relation integration tests

* test: fix cleanup errors

* fix: linting fixes

* fix: machine agents wait

* fix: duplicate remove offer

* fix: duplicate remove app

* fix: force delete app

* fix: jenkins cve

* chore: num units 3

* fix: try wait_for_active

* fix: idle periods

* feat: patched version build

* chore: revert jenkins version

* test: try no slow update-status hook

* fix: async with

* chore: rename fixtures for clarity

* fix: missing change

* fix: conftests
  • Loading branch information
yanksyoon committed Jul 17, 2023
1 parent 4c9de15 commit 64120b6
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 46 deletions.
1 change: 1 addition & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CVE-2023-27899
CVE-2023-27900
CVE-2023-27901
CVE-2023-35141
CVE-2023-2976
# Jenkins plugin manager CVEs
CVE-2022-45688
CVE-2023-20862
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def pytest_addoption(parser: pytest.Parser):
# The Jenkins image name:tag.
parser.addoption("--jenkins-image", action="store", default="")
# The number of jenkins agents to deploy and relate.
parser.addoption("--num-units", action="store", default="1")
parser.addoption("--num-units", action="store", default="3")
138 changes: 113 additions & 25 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pytest_operator.plugin import OpsTest

import jenkins
import state

from .types_ import ModelAppUnit

Expand Down Expand Up @@ -63,12 +64,18 @@ async def application_fixture(
# Deploy the charm and wait for active/idle status
application = await model.deploy(charm, resources=resources, series="jammy")
await model.wait_for_idle(
apps=[application.name], status="active", raise_on_blocked=True, timeout=20 * 60
apps=[application.name],
wait_for_active=True,
raise_on_blocked=True,
timeout=20 * 60,
idle_period=30,
)

yield application
# slow down update-status so that it doesn't intervene currently running tests
async with ops_test.fast_forward(fast_interval="1h"):
yield application

await model.remove_application(application.name, block_until_done=True)
await model.remove_application(application.name, force=True, block_until_done=True)


@pytest_asyncio.fixture(scope="module", name="unit_ip")
Expand All @@ -89,14 +96,38 @@ async def web_address_fixture(unit_ip: str):
return f"http://{unit_ip}:8080"


@pytest.fixture(scope="function", name="app_suffix")
def app_suffix_fixture():
"""Get random 4 char length application suffix."""
# secrets random hex cannot be used because it has chances to generate numeric only suffix
# which will return "<application-name> is not a valid application tag"
app_suffix = "".join(random.choices(string.ascii_lowercase, k=4)) # nosec
return app_suffix


@pytest_asyncio.fixture(scope="function", name="jenkins_k8s_agent")
async def jenkins_k8s_agent_fixture(
model: Model, num_units: int
model: Model, app_suffix: str
) -> typing.AsyncGenerator[Application, None]:
"""The Jenkins k8s agent."""
# secrets random hex cannot be used because it has chances to generate numeric only suffix
# which will return "<application-name> is not a valid application tag"
app_suffix = "".join(random.choices(string.ascii_lowercase, k=4)) # nosec
agent_app: Application = await model.deploy(
"jenkins-agent-k8s",
config={"jenkins_agent_labels": "k8s"},
channel="latest/edge",
application_name=f"jenkins-agentk8s-{app_suffix}",
)
await model.wait_for_idle(apps=[agent_app.name], status="blocked")

yield agent_app

await model.remove_application(agent_app.name, force=True)


@pytest_asyncio.fixture(scope="function", name="new_relation_k8s_agents")
async def new_relation_k8s_agents_fixture(
model: Model, num_units: int, app_suffix: str
) -> typing.AsyncGenerator[Application, None]:
"""The Jenkins k8s agent to be used for new agent relation with multiple units."""
agent_app: Application = await model.deploy(
"jenkins-agent-k8s",
config={"jenkins_agent_labels": "k8s"},
Expand All @@ -111,6 +142,23 @@ async def jenkins_k8s_agent_fixture(
await model.remove_application(agent_app.name, force=True)


@pytest_asyncio.fixture(scope="function", name="new_relation_k8s_agents_related")
async def new_relation_k8s_agents_related_fixture(
model: Model,
new_relation_k8s_agents: Application,
application: Application,
):
"""The Jenkins-k8s server charm related to Jenkins-k8s agent charm through agent relation."""
await application.relate(
state.AGENT_RELATION, f"{new_relation_k8s_agents.name}:{state.AGENT_RELATION}"
)
await model.wait_for_idle(
apps=[application.name, new_relation_k8s_agents.name], wait_for_active=True
)

return application


@pytest_asyncio.fixture(scope="module", name="jenkins_client")
async def jenkins_client_fixture(
application: Application,
Expand Down Expand Up @@ -185,16 +233,71 @@ async def machine_model_fixture(


@pytest_asyncio.fixture(scope="function", name="jenkins_machine_agent")
async def jenkins_machine_agent_fixture(machine_model: Model) -> Application:
async def jenkins_machine_agent_fixture(
machine_model: Model, app_suffix: str
) -> typing.AsyncGenerator[Application, None]:
"""The jenkins machine agent."""
# 2023-06-02 use the edge version of jenkins agent until the changes have been promoted to
# stable.
app = await machine_model.deploy(
"jenkins-agent", channel="latest/edge", config={"labels": "machine"}
"jenkins-agent",
channel="latest/edge",
config={"labels": "machine"},
application_name=f"jenkins-agent-{app_suffix}",
)
await machine_model.create_offer(f"{app.name}:slave")
await machine_model.wait_for_idle(apps=[app.name], status="blocked", timeout=1200)

return app
yield app

await machine_model.remove_offer(f"admin/{machine_model.name}.{app.name}", force=True)
await machine_model.remove_application(app.name, force=True)


@pytest_asyncio.fixture(scope="function", name="new_relation_machine_agents")
async def new_relation_machine_agents_fixture(
machine_model: Model, num_units: int, app_suffix: str
) -> typing.AsyncGenerator[Application, None]:
"""The jenkins machine agent with 3 units to be used for new agent relation."""
# 2023-06-02 use the edge version of jenkins agent until the changes have been promoted to
# stable.
app: Application = await machine_model.deploy(
"jenkins-agent",
channel="latest/edge",
config={"labels": "machine"},
application_name=f"jenkins-agent-{app_suffix}",
num_units=num_units,
)
await machine_model.create_offer(f"{app.name}:{state.AGENT_RELATION}")
await machine_model.wait_for_idle(
apps=[app.name], status="blocked", idle_period=30, timeout=1200
)

yield app

await machine_model.remove_offer(f"admin/{machine_model.name}.{app.name}", force=True)
await machine_model.remove_application(app.name, force=True, block_until_done=True)


@pytest_asyncio.fixture(scope="function", name="new_relation_agent_related")
async def new_relation_agents_related_fixture(
model: Model,
new_relation_machine_agents: Application,
application: Application,
):
"""The Jenkins-k8s server charm related to Jenkins agent charm through agent relation."""
machine_model: Model = new_relation_machine_agents.model
await machine_model.create_offer(f"{new_relation_machine_agents.name}:{state.AGENT_RELATION}")
await model.relate(
f"{application.name}:{state.AGENT_RELATION}",
f"localhost:admin/{machine_model.name}.{new_relation_machine_agents.name}",
)
await machine_model.wait_for_idle(
apps=[new_relation_machine_agents.name], wait_for_active=True
)
await model.wait_for_idle(apps=[application.name], wait_for_active=True)

return application


@pytest.fixture(scope="module", name="jenkins_version")
Expand Down Expand Up @@ -287,18 +390,3 @@ def update_status_env_fixture(model: Model, unit: Unit) -> typing.Iterable[str]:
f"JUJU_MODEL_NAME={model.name}",
f"JUJU_UNIT_NAME={unit.name}",
)


@pytest_asyncio.fixture(scope="function", name="jenkins_k8s_agent_related")
async def jenkins_k8s_agent_related_fixture(
model: Model,
jenkins_k8s_agent: Application,
application: Application,
):
"""The Jenkins-k8s server charm related to Jenkins-k8s agent charm through agent relation."""
await application.relate("agent", f"{jenkins_k8s_agent.name}:agent")
await model.wait_for_idle(
apps=[application.name, jenkins_k8s_agent.name], wait_for_active=True
)

return application
104 changes: 84 additions & 20 deletions tests/integration/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def test_jenkins_wizard_bypass(web_address: str):
assert "Welcome to Jenkins!" in str(response.content)


async def test_jenkins_deprecated_agent_relation(
async def test_jenkins_k8s_deprecated_agent_relation(
model: Model,
application: Application,
jenkins_k8s_agent: Application,
Expand All @@ -45,7 +45,9 @@ async def test_jenkins_deprecated_agent_relation(
assert: the relation succeeds and the agent is able to run jobs successfully.
"""
await application.relate(state.DEPRECATED_AGENT_RELATION, f"{jenkins_k8s_agent.name}")
await model.wait_for_idle(status="active")
await model.wait_for_idle(
apps=[application.name, jenkins_k8s_agent.name], wait_for_active=True
)

nodes = jenkins_client.get_nodes()
assert any(
Expand All @@ -66,22 +68,22 @@ async def test_jenkins_machine_deprecated_agent_relation(
gen_jenkins_test_job_xml: typing.Callable[[str], str],
):
"""
arrange: given a cross controller cross model jenkins machine agent.
act: when the offer is created and relation is setup through the offer.
arrange: given a cross controller cross model jenkins machine agent with an offer.
act: when the relation is setup through the offer.
assert: the relation succeeds and the agent is able to run jobs successfully.
"""
machine_model: Model = jenkins_machine_agent.model
await machine_model.create_offer(f"{jenkins_machine_agent.name}:slave")
model: Model = application.model
await model.relate(
f"{application.name}:{state.DEPRECATED_AGENT_RELATION}",
machine_model: Model = jenkins_machine_agent.model
await application.relate(
state.DEPRECATED_AGENT_RELATION,
f"localhost:admin/{machine_model.name}.{jenkins_machine_agent.name}",
)
await model.wait_for_idle(status="active", timeout=1200)
await model.wait_for_idle(apps=[application.name], wait_for_active=True)
await machine_model.wait_for_idle(apps=[jenkins_machine_agent.name], wait_for_active=True)

nodes = jenkins_client.get_nodes()
assert any(
("jenkins-agent-0" in key for key in nodes.keys())
(jenkins_machine_agent.name in key for key in nodes.keys())
), "Jenkins agent node not registered."

job = jenkins_client.create_job(
Expand All @@ -96,7 +98,7 @@ async def test_jenkins_machine_deprecated_agent_relation(
async def test_jenkins_k8s_agent_relation(
model: Model,
application: Application,
jenkins_k8s_agent: Application,
new_relation_k8s_agents: Application,
jenkins_client: jenkinsapi.jenkins.Jenkins,
gen_jenkins_test_job_xml: typing.Callable[[str], str],
):
Expand All @@ -105,15 +107,17 @@ async def test_jenkins_k8s_agent_relation(
act: when the server charm is related to the k8s agent charm.
assert: the relation succeeds and the agent is able to run jobs successfully.
"""
await application.relate("agent", f"{jenkins_k8s_agent.name}")
await model.wait_for_idle(status="active")
await application.relate(state.AGENT_RELATION, new_relation_k8s_agents.name)
await model.wait_for_idle(
apps=[application.name, new_relation_k8s_agents.name], wait_for_active=True
)

nodes = jenkins_client.get_nodes()
assert any(
(jenkins_k8s_agent.name in key for key in nodes.keys())
(new_relation_k8s_agents.name in key for key in nodes.keys())
), "Jenkins k8s agent node not registered."

job = jenkins_client.create_job(jenkins_k8s_agent.name, gen_jenkins_test_job_xml("k8s"))
job = jenkins_client.create_job(new_relation_k8s_agents.name, gen_jenkins_test_job_xml("k8s"))
queue_item = job.invoke()
queue_item.block_until_complete()
build: jenkinsapi.build.Build = queue_item.get_build()
Expand All @@ -122,17 +126,77 @@ async def test_jenkins_k8s_agent_relation(

async def test_jenkins_k8s_agent_relation_removed(
model: Model,
jenkins_k8s_agent_related: Application,
jenkins_k8s_agent: Application,
new_relation_k8s_agents_related: Application,
new_relation_k8s_agents: Application,
jenkins_client: jenkinsapi.jenkins.Jenkins,
):
"""
arrange: given a jenkins server charm related to jenkins-k8s-agent charm via agent relation.
act: when the relation is removed.
assert: no agent nodes remain registered.
"""
await jenkins_k8s_agent_related.remove_relation("agent", f"{jenkins_k8s_agent.name}:agent")
await model.wait_for_idle()
await new_relation_k8s_agents_related.remove_relation(
state.AGENT_RELATION, f"{new_relation_k8s_agents.name}:{state.AGENT_RELATION}"
)
await model.wait_for_idle(
apps=[new_relation_k8s_agents_related.name, new_relation_k8s_agents.name]
)

nodes = jenkins_client.get_nodes()
assert not any((new_relation_k8s_agents.name in key for key in nodes.keys()))


async def test_jenkins_machine_agent_relation(
model: Model,
application: Application,
new_relation_machine_agents: Application,
jenkins_client: jenkinsapi.jenkins.Jenkins,
gen_jenkins_test_job_xml: typing.Callable[[str], str],
):
"""
arrange: given a cross controller cross model jenkins machine agent with an offer.
act: when the relation is setup through the offer.
assert: the relation succeeds and the agent is able to run jobs successfully.
"""
machine_model: Model = new_relation_machine_agents.model
await application.relate(
state.AGENT_RELATION,
f"localhost:admin/{machine_model.name}.{new_relation_machine_agents.name}",
)
await model.wait_for_idle(apps=[application.name], wait_for_active=True)
await machine_model.wait_for_idle(
apps=[new_relation_machine_agents.name], wait_for_active=True
)

nodes = jenkins_client.get_nodes()
assert any(
(new_relation_machine_agents.name in key for key in nodes.keys())
), "Jenkins agent nodes not registered."

job = jenkins_client.create_job(
new_relation_machine_agents.name, gen_jenkins_test_job_xml("machine")
)
queue_item = job.invoke()
queue_item.block_until_complete()
build: jenkinsapi.build.Build = queue_item.get_build()
assert build.get_status() == "SUCCESS"


async def test_jenkins_machine_agent_relation_removed(
model: Model,
new_relation_agent_related: Application,
new_relation_machine_agents: Application,
jenkins_client: jenkinsapi.jenkins.Jenkins,
):
"""
arrange: given jenkins server charm related to jenkins-agent machine charm via agent relation.
act: when the relation is removed.
assert: no agent nodes remain registered.
"""
await new_relation_agent_related.remove_relation(
state.AGENT_RELATION, f"{new_relation_machine_agents.name}:{state.AGENT_RELATION}"
)
await model.wait_for_idle(apps=[new_relation_agent_related.name])

nodes = jenkins_client.get_nodes()
assert not any((jenkins_k8s_agent.name in key for key in nodes.keys()))
assert not any((new_relation_machine_agents.name in key for key in nodes.keys()))

0 comments on commit 64120b6

Please sign in to comment.