26
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
28
29
+ import base64
30
+ import json
29
31
import os
30
32
import subprocess
31
33
import sys
@@ -84,9 +86,9 @@ def test_model_reload(self):
84
86
self .assertFalse (client .is_model_ready (ensemble_model_name ))
85
87
86
88
87
- class InputValidationTest (unittest .TestCase ):
89
+ class ModelIDValidationTest (unittest .TestCase ):
88
90
"""
89
- Test input validation for user-provided inputs
91
+ Test model ID validation for user-provided model names
90
92
"""
91
93
92
94
def setUp (self ):
@@ -103,10 +105,39 @@ def setUp(self):
103
105
104
106
def _send_load_model_request (self , model_name ):
105
107
"""Send HTTP request to load model for testing input validation using curl"""
108
+
109
+ # Create simple Triton Python model code
110
+ python_model_code = f"""import triton_python_backend_utils as pb_utils
111
+
112
+ class TritonPythonModel:
113
+ def execute(self, requests):
114
+ print('Hello world from model { model_name } ')
115
+ responses = []
116
+ for request in requests:
117
+ # Simple identity function
118
+ input_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT0")
119
+ out_tensor = pb_utils.Tensor("OUTPUT0", input_tensor.as_numpy())
120
+ responses.append(pb_utils.InferenceResponse([out_tensor]))
121
+ return responses"""
122
+
123
+ # Base64 encode the Python code (as required by Triton server)
124
+ python_code_b64 = base64 .b64encode (python_model_code .encode ("utf-8" )).decode (
125
+ "ascii"
126
+ )
127
+
128
+ # Create simple config
129
+ config = {
130
+ "name" : model_name ,
131
+ "backend" : "python" ,
132
+ "max_batch_size" : 4 ,
133
+ "input" : [{"name" : "INPUT0" , "data_type" : "TYPE_FP32" , "dims" : [- 1 ]}],
134
+ "output" : [{"name" : "OUTPUT0" , "data_type" : "TYPE_FP32" , "dims" : [- 1 ]}],
135
+ }
136
+
106
137
payload = {
107
138
"parameters" : {
108
- "config" : f'{{"name": " { model_name } ", "backend": "python", "max_batch_size": 4}}' ,
109
- "file:/1/model.py" : "print('Hello from Python Model')" ,
139
+ "config" : json . dumps ( config ) ,
140
+ "file:/1/model.py" : python_code_b64 ,
110
141
}
111
142
}
112
143
@@ -121,25 +152,18 @@ def _send_load_model_request(self, model_name):
121
152
"curl" ,
122
153
"-s" ,
123
154
"-w" ,
124
- "\n %{http_code}" ,
155
+ "\n %{http_code}" , # Write HTTP status code on separate line
125
156
"-X" ,
126
157
"POST" ,
127
158
"-H" ,
128
159
"Content-Type: application/json" ,
129
160
"-d" ,
130
161
payload_json ,
131
- "--connect-timeout" ,
132
- "10" ,
162
+ url ,
133
163
]
134
164
135
- # Add the URL as a separate argument to avoid shell interpretation issues
136
- curl_cmd .append (url )
137
-
138
- # Debug: print the exact URL being requested
139
- print (f"DEBUG: Curl URL: { url } " )
140
-
141
165
result = subprocess .run (
142
- curl_cmd , capture_output = True , text = True , timeout = 15
166
+ curl_cmd , capture_output = True , text = True , timeout = 10
143
167
)
144
168
145
169
# Parse curl output - last line is status code, rest is response body
@@ -187,16 +211,24 @@ def test_invalid_character_model_names(self):
187
211
"""Test that model names with invalid characters are properly rejected"""
188
212
189
213
# Model names with various invalid characters that should be rejected
214
+ # Based on INVALID_CHARS = ";|&$<>(){}\\\"'`*?~#!"
190
215
invalid_model_names = [
191
- "model$(test)" ,
192
- "model\{test\}" ,
193
- "model`test`" ,
194
- "model;test" ,
195
- "model|test" ,
196
- "model&test" ,
197
- "model'test'" ,
198
- "model*test" ,
199
- "model!test" ,
216
+ r"model;test" ,
217
+ r"model|test" ,
218
+ r"model&test" ,
219
+ r"model$test" ,
220
+ r"model<test>" ,
221
+ r"model(test)" ,
222
+ r"model{test}" ,
223
+ r"model\test" ,
224
+ r'model"test"' ,
225
+ r"model'test'" ,
226
+ r"model`test`" ,
227
+ r"model*test" ,
228
+ # r"model?test", # curl fails to send this request
229
+ r"model~test" ,
230
+ # r"model#test", # curl fails to send this request
231
+ r"model!test" ,
200
232
]
201
233
202
234
for invalid_name in invalid_model_names :
@@ -215,11 +247,32 @@ def test_invalid_character_model_names(self):
215
247
f"Invalid model name '{ invalid_name } ' should not get 200 OK response" ,
216
248
)
217
249
218
- self .assertIn (
219
- "Invalid stub name: contains invalid characters" ,
220
- response .text ,
221
- f"invalid response for '{ invalid_name } ' should contain 'Invalid stub name: contains invalid characters'" ,
222
- )
250
+ # Special case for curly braces - they get stripped and cause load failures prior to the validation check
251
+ if "{" in invalid_name or "}" in invalid_name :
252
+ self .assertIn (
253
+ "failed to load" ,
254
+ response .text ,
255
+ f"Model with curly braces '{ invalid_name } ' should fail to load" ,
256
+ )
257
+ else :
258
+ # Normal case - should get character validation error
259
+ self .assertIn (
260
+ "Invalid stub name: contains invalid characters" ,
261
+ response .text ,
262
+ f"invalid response for '{ invalid_name } ' should contain 'Invalid stub name: contains invalid characters'" ,
263
+ )
264
+
265
+ # Verify the model is not loaded/ready since it was rejected
266
+ try :
267
+ self .assertFalse (
268
+ self ._client .is_model_ready (invalid_name ),
269
+ f"Model '{ invalid_name } ' should not be ready after failed load attempt" ,
270
+ )
271
+ except Exception as e :
272
+ # If checking model readiness fails, that's also acceptable since the model name is invalid
273
+ print (
274
+ f"Note: Could not check model readiness for '{ invalid_name } ': { e } "
275
+ )
223
276
224
277
def test_valid_model_names (self ):
225
278
"""Test that valid model names work"""
@@ -239,15 +292,31 @@ def test_valid_model_names(self):
239
292
f"Response for valid '{ valid_name } ': Status { response .status_code } , Text: { response .text [:100 ]} ..."
240
293
)
241
294
242
- # Valid names might still fail for other reasons (model doesn't exist, etc.)
243
- # but they should not be rejected due to character validation
244
- # We just check it's not a validation error
295
+ # Valid model names should be accepted and load successfully
296
+ self .assertEqual (
297
+ 200 ,
298
+ response .status_code ,
299
+ f"Valid model name '{ valid_name } ' should get 200 OK response, got { response .status_code } . Response: { response .text } " ,
300
+ )
301
+
302
+ # Should not contain validation error message
245
303
self .assertNotIn (
246
304
"Invalid stub name: contains invalid characters" ,
247
305
response .text ,
248
- f"valid response for '{ valid_name } ' should not contain 'Invalid stub name: contains invalid characters' " ,
306
+ f"Valid model name '{ valid_name } ' should not contain validation error message " ,
249
307
)
250
308
309
+ # Verify the model is actually loaded by checking if it's ready
310
+ try :
311
+ self .assertTrue (
312
+ self ._client .is_model_ready (valid_name ),
313
+ f"Model '{ valid_name } ' should be ready after successful load" ,
314
+ )
315
+ # Clean up - unload the model after testing
316
+ self ._client .unload_model (valid_name )
317
+ except Exception as e :
318
+ self .fail (f"Failed to check if model '{ valid_name } ' is ready: { e } " )
319
+
251
320
252
321
if __name__ == "__main__" :
253
322
unittest .main ()
0 commit comments