Skip to content

Commit 55e5136

Browse files
committed
fix(trainer): detect OpenShift pip permission error and document workaround
Signed-off-by: priyank <priyank8445@gmail.com>
1 parent d70b7a4 commit 55e5136

4 files changed

Lines changed: 91 additions & 1 deletion

File tree

kubeflow_mcp/trainer/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@
370370
371371
PLATFORM:
372372
- If pre_flight() returns platform=openshift, ALWAYS pass emptyDir volumes for /.local, /.cache, /tmp — without these, jobs fail on read-only filesystem. Read trainer://guides/platform-fixes for copy-paste JSON
373+
- OpenShift packages restriction: if platform=openshift, do NOT use the packages parameter in run_custom_training() as the pre-script pip install step will fail with PermissionError on /.local. Instead, install packages inside your training script to /workspace/lib using subprocess and append to sys.path
373374
- Gated HuggingFace models require hf_token parameter
374375
- Training jobs consume GPU resources — be conservative with num_nodes
375376
- Use get_training_events() to debug stuck/failed jobs. Read trainer://guides/troubleshooting for error-to-fix tables""",

kubeflow_mcp/trainer/api/monitoring.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858
"FILE_NOT_FOUND",
5959
"Check dataset/model paths and volume mounts.",
6060
),
61+
(
62+
re.compile(
63+
r"PermissionError: \[Errno 13\] Permission denied: '/\.local|Permission denied: '/\.local",
64+
re.IGNORECASE,
65+
),
66+
"OPENSHIFT_PIP_ERROR",
67+
"On OpenShift under a restricted SCC, pip install --user fails on read-only /.local. Do NOT use the 'packages' parameter in run_custom_training(). Instead, install packages inside your script to /workspace/lib using subprocess and append to sys.path. Read trainer://guides/platform-fixes for details.",
68+
),
6169
(
6270
re.compile(r"PermissionError|Access Denied", re.IGNORECASE),
6371
"PERMISSION_ERROR",

kubeflow_mcp/trainer/resources/platform-fixes.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,24 @@ For `fine_tune()`, these volumes apply to ALL replicated jobs (node, dataset-ini
9696
"env": {"NCCL_P2P_DISABLE": "1", "NCCL_SHM_DISABLE": "1"}
9797
```
9898

99-
**4. If emptyDirs are not enough** — escalate to cluster admin:
99+
**4. Pip package installation permission denied in run_custom_training()** — when using `run_custom_training(packages=[...])` on OpenShift under a restricted SCC, the SDK's pre-script pip install step attempts to run `pip install --user` which writes to `/.local`. Since user-defined emptyDir volumes are not mounted on the training container during this step, the job will fail immediately with:
100+
`PermissionError: [Errno 13] Permission denied: '/.local'`
101+
102+
*Workaround*: Do **NOT** use the `packages` parameter on OpenShift. Instead, write a workaround directly in your training script to install the required packages to `/workspace/lib` (which is a writable emptyDir) and append it to `sys.path`:
103+
104+
```python
105+
import subprocess, sys, os
106+
lib_dir = '/workspace/lib'
107+
os.makedirs(lib_dir, exist_ok=True)
108+
subprocess.run([
109+
sys.executable, '-m', 'pip', 'install',
110+
'--target', lib_dir, '--quiet',
111+
'transformers', 'peft', 'trl'
112+
], check=True)
113+
sys.path.insert(0, lib_dir)
114+
```
115+
116+
**5. If emptyDirs are not enough** — escalate to cluster admin:
100117

101118
```bash
102119
oc adm policy add-scc-to-user anyuid -z <service-account> -n <namespace>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright The Kubeflow 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+
15+
"""Unit tests for log monitoring and failure pattern extraction."""
16+
17+
from kubeflow_mcp.trainer.api.monitoring import _extract_failure_hint
18+
19+
20+
def test_extract_failure_hint_openshift_pip_error():
21+
# Test exact PermissionError trace
22+
logs = (
23+
"Installing collected packages: torch\n"
24+
"PermissionError: [Errno 13] Permission denied: '/.local'\n"
25+
"ERROR: Job failed"
26+
)
27+
hint = _extract_failure_hint(logs)
28+
assert hint is not None
29+
assert hint["category"] == "OPENSHIFT_PIP_ERROR"
30+
assert "On OpenShift under a restricted SCC" in hint["suggestion"]
31+
assert "Do NOT use the 'packages' parameter" in hint["suggestion"]
32+
33+
# Test generic permission denied on /.local
34+
logs_generic = "Permission denied: '/.local/bin/pip'"
35+
hint_generic = _extract_failure_hint(logs_generic)
36+
assert hint_generic is not None
37+
assert hint_generic["category"] == "OPENSHIFT_PIP_ERROR"
38+
39+
40+
def test_extract_failure_hint_generic_permission_error():
41+
# Test a generic permission error does not trigger OpenShift pip error
42+
logs = "PermissionError: [Errno 13] Permission denied: '/workspace/data.csv'"
43+
hint = _extract_failure_hint(logs)
44+
assert hint is not None
45+
assert hint["category"] == "PERMISSION_ERROR"
46+
assert "Check service account permissions" in hint["suggestion"]
47+
48+
49+
def test_extract_failure_hint_other_patterns():
50+
# Test CUDA OOM
51+
oom_logs = "RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB"
52+
oom_hint = _extract_failure_hint(oom_logs)
53+
assert oom_hint is not None
54+
assert oom_hint["category"] == "OOM"
55+
56+
# Test Missing Module
57+
module_logs = "ModuleNotFoundError: No module named 'peft'"
58+
module_hint = _extract_failure_hint(module_logs)
59+
assert module_hint is not None
60+
assert module_hint["category"] == "MISSING_MODULE"
61+
62+
# Test no match
63+
clean_logs = "Training completed successfully. Epoch 5/5 finished."
64+
assert _extract_failure_hint(clean_logs) is None

0 commit comments

Comments
 (0)