Skip to content
Merged
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
22 changes: 19 additions & 3 deletions .tekton/on-cm-runner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ metadata:
pipelinesascode.tekton.dev/task: "[git-clone, ./.tekton/post-integration-evaluation.yaml]"
pipelinesascode.tekton.dev/max-keep-runs: "100"
spec:
# Cluster-specific: requires p4d.24xlarge GPU nodes with matching taint; update if migrating to a different cluster.
podTemplate:
tolerations:
- key: "p4d-gpu"
operator: "Exists"
effect: "NoSchedule"
nodeSelector:
node.kubernetes.io/instance-type: p4d.24xlarge
Comment on lines +15 to +21

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmihalac This currently works in current cluster, but if you'll move to a new cluster, then it might fail to schedule the task and the pipeline will fail. consider adding a comment about that that this is ocp cluster infrastructure dependent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

timeouts:
pipeline: 10h30m0s # Timeout for the entire PipelineRun
params:
Expand Down Expand Up @@ -124,6 +132,14 @@ spec:
- name: source
- name: basic-auth
- name: exploit-iq-data
# Cluster-specific: requires p4d.24xlarge GPU nodes with matching taint; update if migrating to a different cluster.
podTemplate:
tolerations:
- key: "p4d-gpu"
operator: "Exists"
effect: "NoSchedule"
nodeSelector:
node.kubernetes.io/instance-type: p4d.24xlarge
Comment on lines +136 to +142

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmihalac This currently works in current cluster, but if you'll move to a new cluster, then it might fail to schedule the task and the pipeline will fail. consider adding a comment about that that this is ocp infrastructure dependent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

volumes:
- name: google-creds-volume
secret:
Expand All @@ -137,7 +153,7 @@ spec:
items:
- key: service-ca.crt
path: service-ca.crt # Mounts as a file named service-ca.crt

# >>> THE SERVER (Sidecar) <<<
sidecars:
- name: server-application
Expand All @@ -148,10 +164,10 @@ spec:
resources:
requests:
cpu: "3000m" # CPU request (3 cores)
memory: "12Gi" # Memory request (8 gigabytes)
memory: "32Gi" # Memory request
limits:
cpu: "3000m" # CPU limit (3 cores)
memory: "32Gi" # Memory limit (16 gigabytes)
memory: "64Gi" # Memory limit

volumeMounts:
- name: google-creds-volume
Expand Down
4 changes: 2 additions & 2 deletions kustomize/base/exploit_iq_service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ spec:
resources:
limits:
memory: "8Gi"
cpu: "1000m"
cpu: "2000m"
requests:
memory: "1Gi"
cpu: "1000m"
cpu: "2000m"
env:
- name: SERPAPI_API_KEY
valueFrom:
Expand Down
55 changes: 1 addition & 54 deletions src/exploit_iq_commons/utils/dep_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,60 +56,7 @@
from exploit_iq_commons.logging.loggers_factory import LoggingFactory


def _available_cpus() -> int:
"""Return the number of CPUs available to this process.

Respects container CPU limits from cgroup v2/v1 before falling back to
process affinity and Python CPU APIs.
"""

# cgroup v2
try:
with open("/sys/fs/cgroup/cpu.max", encoding="utf-8") as f:
quota_s, period_s = f.read().strip().split()

if quota_s != "max":
quota = int(quota_s)
period = int(period_s)

if quota > 0 and period > 0:
return max(1, math.ceil(quota / period))
except (OSError, ValueError):
pass

# cgroup v1
for base in (
"/sys/fs/cgroup/cpu",
"/sys/fs/cgroup/cpu,cpuacct",
):
try:
with open(f"{base}/cpu.cfs_quota_us", encoding="utf-8") as f:
quota = int(f.read().strip())

with open(f"{base}/cpu.cfs_period_us", encoding="utf-8") as f:
period = int(f.read().strip())

# cgroup v1 uses quota == -1 to mean "no CPU quota".
if quota > 0 and period > 0:
return max(1, math.ceil(quota / period))
except (OSError, ValueError):
pass

# CPU affinity fallback.
try:
return max(1, len(os.sched_getaffinity(0)))
except (AttributeError, OSError):
pass

# Python 3.13+ fallback.
# Keep this after cgroup checks: in OpenShift/Kubernetes it may expose
# the full node-visible CPU set rather than the pod CPU limit.
if hasattr(os, "process_cpu_count"):
count = os.process_cpu_count()
if count:
return max(1, count)

return os.cpu_count() or 4
from exploit_iq_commons.utils.system_utils import available_cpus as _available_cpus

def _extract_source_jar(jar: Path, dest: Path) -> None:
"""Extract a single source JAR into dest directory."""
Expand Down
28 changes: 24 additions & 4 deletions src/exploit_iq_commons/utils/source_code_git_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import contextlib
import os
import tempfile
import time
import threading
import typing
import urllib.parse
Expand Down Expand Up @@ -79,6 +80,8 @@
logger = LoggingFactory.get_agent_logger(__name__)

_gitconfig_lock = threading.Lock()
_MAX_GITCONFIG_RETRIES = 5
_GITCONFIG_RETRY_DELAY = 0.2

def _ssh_known_hosts_path() -> Path:
"""Resolve the managed known_hosts path; fail closed if missing or empty.
Expand Down Expand Up @@ -263,11 +266,28 @@ def load_repo(self):

# After "RUN git config --global --add safe.directory '*'" was removed from Dockerfile, need to flag the directory that passed all the
# security checks as a safe directory.
# resolve() follows symlinks so the path matches what git sees internally
# (e.g. .cache/am_cache -> /exploit-iq-data symlink in OpenShift).
resolved_path = str(self.repo_path.resolve())
# _gitconfig_lock serializes within this process; the retry loop handles
# cross-process contention (worker subprocesses each have their own lock).
with _gitconfig_lock:
global_config = git.config.get_config_path("global")
config = git.GitConfigParser(global_config, read_only=False)
with config:
config.add_value("safe", "directory", str(self.repo_path.resolve()))
for attempt in range(_MAX_GITCONFIG_RETRIES):
try:
global_config = git.config.get_config_path("global")
config = git.GitConfigParser(global_config, read_only=False)
with config:
# Dedup: skip if path already registered to prevent unbounded growth.
existing = config.get_values("safe", "directory") if config.has_section("safe") else []
if resolved_path not in existing:
config.add_value("safe", "directory", resolved_path)
break
except OSError:
# GitPython raises OSError when another process holds .gitconfig.lock.
if attempt < _MAX_GITCONFIG_RETRIES - 1:
time.sleep(_GITCONFIG_RETRY_DELAY * (attempt + 1))
else:
raise

if not os.path.exists(self.repo_path) and self.clone_url is None:
raise ValueError(f"Path {self.repo_path} does not exist")
Expand Down
73 changes: 73 additions & 0 deletions src/exploit_iq_commons/utils/system_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import os


def available_cpus() -> int:
"""Return the number of CPUs available to this process.

Respects container CPU limits from cgroup v2/v1 before falling back to
process affinity and Python CPU APIs.
"""

# cgroup v2
try:
with open("/sys/fs/cgroup/cpu.max", encoding="utf-8") as f:
quota_s, period_s = f.read().strip().split()

if quota_s != "max":
quota = int(quota_s)
period = int(period_s)

if quota > 0 and period > 0:
return max(1, math.ceil(quota / period))
except (OSError, ValueError):
pass

# cgroup v1
for base in (
"/sys/fs/cgroup/cpu",
"/sys/fs/cgroup/cpu,cpuacct",
):
try:
with open(f"{base}/cpu.cfs_quota_us", encoding="utf-8") as f:
quota = int(f.read().strip())

with open(f"{base}/cpu.cfs_period_us", encoding="utf-8") as f:
period = int(f.read().strip())

# cgroup v1 uses quota == -1 to mean "no CPU quota".
if quota > 0 and period > 0:
return max(1, math.ceil(quota / period))
except (OSError, ValueError):
pass

# CPU affinity fallback.
try:
return max(1, len(os.sched_getaffinity(0)))
except (AttributeError, OSError):
pass

# Python 3.13+ fallback.
# Keep this after cgroup checks: in OpenShift/Kubernetes it may expose
# the full node-visible CPU set rather than the pod CPU limit.
if hasattr(os, "process_cpu_count"):
count = os.process_cpu_count()
if count:
return max(1, count)

return os.cpu_count() or 4
Loading