Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__pycache__/
**/__pycache__
2 changes: 2 additions & 0 deletions _prow_mcp_server/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ COPY mcp_server.py ./
COPY drain.py ./
COPY drain3.ini ./

EXPOSE 9000

CMD ["python", "mcp_server.py"]
2 changes: 1 addition & 1 deletion _prow_mcp_server/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"localhost/mcp-server:latest"
],
"env": {
"MCP_TRANSPORT": "stdio"
"MCP_TRANSPORT": "streamable-http"
}
}
}
Expand Down
135 changes: 127 additions & 8 deletions _prow_mcp_server/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,139 @@
import asyncio
from typing import Any, Optional, Dict
from dateutil.parser import parse as parse_date
import re

from drain import DrainExtractor

import httpx
from mcp.server.fastmcp import FastMCP
from fastmcp import FastMCP

mcp = FastMCP("prow-mcp-server")
mcp = FastMCP(name="prow-mcp-server", stateless_http=True, host="0.0.0.0", port=9000)

GCS_URL = "https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/logs"

_drain_extractor: Optional['DrainExtractor'] = None

def extract_installation_info(log_content: str) -> Dict[str, Any]:
"""Extract installation information from build-log.txt."""
install_info = {
"installer_version": None,
"installer_commit": None,
"release_image": None,
"instance_types": {},
"install_duration": None,
"architecture": None,
"cluster_config": {},
"install_success": False
}

# Extract openshift-install version and commit (can be on separate lines)
version_patterns = [
r'openshift-install v([^\s"]+)',
r'"openshift-install v([^\s"]+)"'
]

for pattern in version_patterns:
version_match = re.search(pattern, log_content)
if version_match:
install_info["installer_version"] = version_match.group(1)
break

# Extract commit (separate pattern)
commit_patterns = [
r'built from commit ([a-f0-9]+)',
r'"built from commit ([a-f0-9]+)"'
]

for pattern in commit_patterns:
commit_match = re.search(pattern, log_content)
if commit_match:
install_info["installer_commit"] = commit_match.group(1)
break

# Extract release image
release_patterns = [
r'Installing from release ([^\s]+)',
r'release image "([^"]+)"',
r'RELEASE_IMAGE_LATEST for release image "([^"]+)"'
]
for pattern in release_patterns:
release_match = re.search(pattern, log_content)
if release_match:
install_info["release_image"] = release_match.group(1)
break

# Extract instance types from install-config.yaml section
# Look for compute and controlPlane sections
compute_type_pattern = r'compute:.*?type:\s*([^\s\n]+)'
control_type_pattern = r'controlPlane:.*?type:\s*([^\s\n]+)'

compute_match = re.search(compute_type_pattern, log_content, re.DOTALL)
if compute_match:
install_info["instance_types"]["compute"] = compute_match.group(1)

control_match = re.search(control_type_pattern, log_content, re.DOTALL)
if control_match:
install_info["instance_types"]["control_plane"] = control_match.group(1)

# Extract architecture
arch_pattern = r'architecture:\s*([^\s\n]+)'
arch_match = re.search(arch_pattern, log_content)
if arch_match:
install_info["architecture"] = arch_match.group(1)

# Extract cluster configuration details
# Replicas
compute_replicas_pattern = r'compute:.*?replicas:\s*(\d+)'
control_replicas_pattern = r'controlPlane:.*?replicas:\s*(\d+)'

compute_replicas_match = re.search(compute_replicas_pattern, log_content, re.DOTALL)
if compute_replicas_match:
install_info["cluster_config"]["compute_replicas"] = int(compute_replicas_match.group(1))

control_replicas_match = re.search(control_replicas_pattern, log_content, re.DOTALL)
if control_replicas_match:
install_info["cluster_config"]["control_replicas"] = int(control_replicas_match.group(1))

# Network type
network_pattern = r'networkType:\s*([^\s\n]+)'
network_match = re.search(network_pattern, log_content)
if network_match:
install_info["cluster_config"]["network_type"] = network_match.group(1)

# Platform and region
platform_pattern = r'platform:\s*([^\s\n]+):'
region_pattern = r'region:\s*([^\s\n]+)'

platform_match = re.search(platform_pattern, log_content)
if platform_match:
install_info["cluster_config"]["platform"] = platform_match.group(1)

region_match = re.search(region_pattern, log_content)
if region_match:
install_info["cluster_config"]["region"] = region_match.group(1)

# Extract install duration (clean up quotes)
duration_patterns = [
r'Time elapsed:\s*([^\n"]+)',
r'Install complete!.*?Time elapsed:\s*([^\n"]+)'
]

for pattern in duration_patterns:
duration_match = re.search(pattern, log_content, re.DOTALL)
if duration_match:
duration = duration_match.group(1).strip().strip('"')
install_info["install_duration"] = duration
break

# Check if installation was successful
if "Install complete!" in log_content:
install_info["install_success"] = True
elif "level=error" in log_content or "FATAL" in log_content:
install_info["install_success"] = False

return install_info

async def make_request(
url: str, method: str = "GET", data: dict[str, Any] = None
) -> dict[str, Any] | None:
Expand Down Expand Up @@ -64,10 +185,7 @@ async def get_job_metadata(job_name: str, build_id: str) -> dict:
for arg in args:
if arg.startswith("--target="):
test_name=arg.replace("--target=","")





return {"status": status, "build_id": build_id, "job_name": job_name, "test_name": test_name}

except Exception as e:
Expand Down Expand Up @@ -247,5 +365,6 @@ async def get_install_logs(job_name: str, build_id: str, test_name: str):
# print(result)

if __name__ == "__main__":
# asyncio.run(main())
mcp.run(transport=os.environ.get("MCP_TRANSPORT", "stdio"))
# mcp.streamable_http_app()
mcp.run(transport="streamable-http", host="0.0.0.0", port=9000)

3 changes: 2 additions & 1 deletion _prow_mcp_server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
httpx
fastmcp
fastmcp>=2.10.6
mcp>=1.8.0
python-dateutil
drain3
Loading