- 
                Notifications
    You must be signed in to change notification settings 
- Fork 37
Add failure injection mode to simulator #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
638d0f7
              9ffe957
              a5a7d81
              951f4a3
              6930192
              5ec92b8
              8e0eefa
              75dcb72
              d7bb175
              c35dbca
              2eca8e6
              28fb65b
              3ae7113
              f5ae85b
              9dbb689
              106e276
              5162226
              7bd69e8
              5182187
              b68115f
              bfa02ff
              700e36f
              14860b3
              8f6d56c
              e0183b7
              178a594
              72dde24
              13492fc
              7994048
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /* | ||
| Copyright 2025 The llm-d-inference-sim Authors. | ||
| 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. | ||
| */ | ||
|  | ||
| package llmdinferencesim | ||
|  | ||
| import ( | ||
| "fmt" | ||
|  | ||
| "github.com/llm-d/llm-d-inference-sim/pkg/common" | ||
| ) | ||
|  | ||
| const ( | ||
| // Error message templates | ||
| RateLimitMessageTemplate = "Rate limit reached for %s in organization org-xxx on requests per min (RPM): Limit 3, Used 3, Requested 1." | ||
| ModelNotFoundMessageTemplate = "The model '%s-nonexistent' does not exist" | ||
| ) | ||
|         
                  smarunich marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| type FailureSpec struct { | ||
| StatusCode int | ||
| ErrorType string | ||
| ErrorCode string | ||
| Message string | ||
| Param *string | ||
| } | ||
|         
                  smarunich marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| var predefinedFailures = map[string]FailureSpec{ | ||
| common.FailureTypeRateLimit: { | ||
|         
                  smarunich marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| StatusCode: 429, | ||
| ErrorType: "rate_limit_exceeded", | ||
| ErrorCode: "rate_limit_exceeded", | ||
| Message: "Rate limit reached for model in organization org-xxx on requests per min (RPM): Limit 3, Used 3, Requested 1.", | ||
|          | ||
| Param: nil, | ||
| }, | ||
| common.FailureTypeInvalidAPIKey: { | ||
| StatusCode: 401, | ||
| ErrorType: "invalid_request_error", | ||
| ErrorCode: "invalid_api_key", | ||
| Message: "Incorrect API key provided", | ||
| Param: nil, | ||
| }, | ||
| common.FailureTypeContextLength: { | ||
| StatusCode: 400, | ||
| ErrorType: "invalid_request_error", | ||
| ErrorCode: "context_length_exceeded", | ||
| Message: "This model's maximum context length is 4096 tokens. However, your messages resulted in 4500 tokens.", | ||
| Param: stringPtr("messages"), | ||
| }, | ||
| common.FailureTypeServerError: { | ||
| StatusCode: 503, | ||
| ErrorType: "server_error", | ||
| ErrorCode: "server_error", | ||
| Message: "The server is overloaded or not ready yet.", | ||
| Param: nil, | ||
| }, | ||
| common.FailureTypeInvalidRequest: { | ||
| StatusCode: 400, | ||
| ErrorType: "invalid_request_error", | ||
| ErrorCode: "invalid_request_error", | ||
| Message: "Invalid request: missing required parameter 'model'.", | ||
| Param: stringPtr("model"), | ||
| }, | ||
| common.FailureTypeModelNotFound: { | ||
| StatusCode: 404, | ||
| ErrorType: "invalid_request_error", | ||
| ErrorCode: "model_not_found", | ||
| Message: "The model 'gpt-nonexistent' does not exist", | ||
|          | ||
| Param: stringPtr("model"), | ||
| }, | ||
| } | ||
|  | ||
| // ShouldInjectFailure determines whether to inject a failure based on configuration | ||
| func ShouldInjectFailure(config *common.Configuration) bool { | ||
|          | ||
| if config.FailureInjectionRate == 0 { | ||
| return false | ||
| } | ||
|  | ||
| return common.RandomInt(1, 100) <= config.FailureInjectionRate | ||
| } | ||
|  | ||
| // GetRandomFailure returns a random failure from configured types or all types if none specified | ||
| func GetRandomFailure(config *common.Configuration) FailureSpec { | ||
|         
                  smarunich marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| var availableFailures []string | ||
| if len(config.FailureTypes) == 0 { | ||
| // Use all failure types if none specified | ||
| for failureType := range predefinedFailures { | ||
| availableFailures = append(availableFailures, failureType) | ||
| } | ||
| } else { | ||
| availableFailures = config.FailureTypes | ||
| } | ||
|  | ||
| if len(availableFailures) == 0 { | ||
| // Fallback to server_error if no valid types | ||
| return predefinedFailures[common.FailureTypeServerError] | ||
| } | ||
|  | ||
| randomIndex := common.RandomInt(0, len(availableFailures)-1) | ||
| randomType := availableFailures[randomIndex] | ||
|  | ||
| // Customize message with current model name | ||
| failure := predefinedFailures[randomType] | ||
| if randomType == common.FailureTypeRateLimit && config.Model != "" { | ||
| failure.Message = fmt.Sprintf(RateLimitMessageTemplate, config.Model) | ||
| } else if randomType == common.FailureTypeModelNotFound && config.Model != "" { | ||
| failure.Message = fmt.Sprintf(ModelNotFoundMessageTemplate, config.Model) | ||
| } | ||
|  | ||
| return failure | ||
| } | ||
|  | ||
| func stringPtr(s string) *string { | ||
| return &s | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| /* | ||
| Copyright 2025 The llm-d-inference-sim Authors. | ||
|  | ||
| 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. | ||
| */ | ||
|  | ||
| package llmdinferencesim_test | ||
|  | ||
| import ( | ||
| "strings" | ||
|  | ||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
|  | ||
| "github.com/llm-d/llm-d-inference-sim/pkg/common" | ||
| llmdinferencesim "github.com/llm-d/llm-d-inference-sim/pkg/llm-d-inference-sim" | ||
| ) | ||
|  | ||
| var _ = Describe("Failures", func() { | ||
| Describe("ShouldInjectFailure", func() { | ||
| It("should not inject failure when injection rate is 0", func() { | ||
| config := &common.Configuration{ | ||
| Mode: common.ModeRandom, | ||
| FailureInjectionRate: 0, | ||
| } | ||
| Expect(llmdinferencesim.ShouldInjectFailure(config)).To(BeFalse()) | ||
| }) | ||
|  | ||
| It("should inject failure when injection rate is 100", func() { | ||
| config := &common.Configuration{ | ||
| Mode: common.ModeRandom, | ||
| FailureInjectionRate: 100, | ||
| } | ||
| Expect(llmdinferencesim.ShouldInjectFailure(config)).To(BeTrue()) | ||
| }) | ||
|  | ||
| }) | ||
|  | ||
| Describe("GetRandomFailure", func() { | ||
| It("should return a failure from all types when none specified", func() { | ||
| config := &common.Configuration{ | ||
| Model: "test-model", | ||
| FailureTypes: []string{}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(BeNumerically(">=", 400)) | ||
| Expect(failure.Message).ToNot(BeEmpty()) | ||
| Expect(failure.ErrorType).ToNot(BeEmpty()) | ||
| }) | ||
|  | ||
| It("should return rate limit failure when specified", func() { | ||
| config := &common.Configuration{ | ||
| Model: "test-model", | ||
| FailureTypes: []string{common.FailureTypeRateLimit}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(Equal(429)) | ||
| Expect(failure.ErrorType).To(Equal("rate_limit_exceeded")) | ||
| Expect(failure.ErrorCode).To(Equal("rate_limit_exceeded")) | ||
| Expect(strings.Contains(failure.Message, "test-model")).To(BeTrue()) | ||
| }) | ||
|  | ||
| It("should return invalid API key failure when specified", func() { | ||
| config := &common.Configuration{ | ||
| FailureTypes: []string{common.FailureTypeInvalidAPIKey}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(Equal(401)) | ||
| Expect(failure.ErrorType).To(Equal("invalid_request_error")) | ||
| Expect(failure.ErrorCode).To(Equal("invalid_api_key")) | ||
| Expect(failure.Message).To(Equal("Incorrect API key provided")) | ||
| }) | ||
|  | ||
| It("should return context length failure when specified", func() { | ||
| config := &common.Configuration{ | ||
| FailureTypes: []string{common.FailureTypeContextLength}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(Equal(400)) | ||
| Expect(failure.ErrorType).To(Equal("invalid_request_error")) | ||
| Expect(failure.ErrorCode).To(Equal("context_length_exceeded")) | ||
| Expect(failure.Param).ToNot(BeNil()) | ||
| Expect(*failure.Param).To(Equal("messages")) | ||
| }) | ||
|  | ||
| It("should return server error when specified", func() { | ||
| config := &common.Configuration{ | ||
| FailureTypes: []string{common.FailureTypeServerError}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(Equal(503)) | ||
| Expect(failure.ErrorType).To(Equal("server_error")) | ||
| Expect(failure.ErrorCode).To(Equal("server_error")) | ||
| }) | ||
|  | ||
| It("should return model not found failure when specified", func() { | ||
| config := &common.Configuration{ | ||
| Model: "test-model", | ||
| FailureTypes: []string{common.FailureTypeModelNotFound}, | ||
| } | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(Equal(404)) | ||
| Expect(failure.ErrorType).To(Equal("invalid_request_error")) | ||
| Expect(failure.ErrorCode).To(Equal("model_not_found")) | ||
| Expect(strings.Contains(failure.Message, "test-model-nonexistent")).To(BeTrue()) | ||
| }) | ||
|  | ||
| It("should return server error as fallback for empty types", func() { | ||
| config := &common.Configuration{ | ||
| FailureTypes: []string{}, | ||
| } | ||
| // This test is probabilistic since it randomly selects, but we can test structure | ||
| failure := llmdinferencesim.GetRandomFailure(config) | ||
| Expect(failure.StatusCode).To(BeNumerically(">=", 400)) | ||
| Expect(failure.ErrorType).ToNot(BeEmpty()) | ||
| }) | ||
| }) | ||
| }) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add json annotation