diff --git a/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml b/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml index 45841d0..858efd0 100644 --- a/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml +++ b/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml @@ -108,6 +108,29 @@ spec: description: Match specifies a set of criterion to be met in order for the rule to be applied to the HTTP request. properties: + contentMatch: + description: ContentMatch + items: + properties: + contents: + description: Content is the content of the fault injection + rule + items: + type: string + type: array + matchType: + type: string + methods: + description: 'Method specifies the http method of + the request, like: PUT, POST, GET, DELETE.' + items: + type: string + type: array + required: + - contents + - methods + type: object + type: array httpMatch: items: description: HttpMatch specifies the criteria for matching diff --git a/pkg/apis/ctrlmesh/constants/constants.go b/pkg/apis/ctrlmesh/constants/constants.go index 27abe5f..1aff96b 100644 --- a/pkg/apis/ctrlmesh/constants/constants.go +++ b/pkg/apis/ctrlmesh/constants/constants.go @@ -63,7 +63,7 @@ const ( EnvEnableSim = "ENABLE_SIM" EnvDisableCircuitBreaker = "DISABLE_CIRCUIT_BREAKER" - EnvDisableFaultInjection = "DISABLE_FAULT_INJECTION" + EnvEnableFaultInjection = "ENABLE_FAULT_INJECTION" EnvEnableApiServerCircuitBreaker = "ENABLE_API_SERVER_BREAKER" EnvEnableRestCircuitBreaker = "ENABLE_REST_BREAKER" EnvEnableRestFaultInjection = "ENABLE_REST_FAULT_INJECTION" @@ -77,6 +77,7 @@ func AllProxySyncEnvKey() []string { EnvIPTable, EnvEnableWebHookProxy, EnvDisableCircuitBreaker, + EnvEnableFaultInjection, EnvEnableApiServerCircuitBreaker, EnvEnableRestCircuitBreaker, EnvEnableRestFaultInjection, diff --git a/pkg/apis/ctrlmesh/proto/faultinjection.pb.go b/pkg/apis/ctrlmesh/proto/faultinjection.pb.go index 626ec48..5aef92d 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection.pb.go +++ b/pkg/apis/ctrlmesh/proto/faultinjection.pb.go @@ -77,6 +77,52 @@ func (FaultInjection_Option) EnumDescriptor() ([]byte, []int) { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{1, 0} } +type StringMatch_StringMatchType int32 + +const ( + StringMatch_NORMAL StringMatch_StringMatchType = 0 + StringMatch_REGEXP StringMatch_StringMatchType = 1 +) + +// Enum value maps for StringMatch_StringMatchType. +var ( + StringMatch_StringMatchType_name = map[int32]string{ + 0: "NORMAL", + 1: "REGEXP", + } + StringMatch_StringMatchType_value = map[string]int32{ + "NORMAL": 0, + "REGEXP": 1, + } +) + +func (x StringMatch_StringMatchType) Enum() *StringMatch_StringMatchType { + p := new(StringMatch_StringMatchType) + *p = x + return p +} + +func (x StringMatch_StringMatchType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StringMatch_StringMatchType) Descriptor() protoreflect.EnumDescriptor { + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_enumTypes[1].Descriptor() +} + +func (StringMatch_StringMatchType) Type() protoreflect.EnumType { + return &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_enumTypes[1] +} + +func (x StringMatch_StringMatchType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StringMatch_StringMatchType.Descriptor instead. +func (StringMatch_StringMatchType) EnumDescriptor() ([]byte, []int) { + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{5, 0} +} + type FaultInjectConfigResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -286,24 +332,29 @@ func (x *HTTPFaultInjection) GetEffectiveTime() *EffectiveTimeRange { return nil } -// EffectiveTimeRange specifies the effective time range for fault injection configuration. +// EffectiveTimeRange specifies the effective time range for fault injection +// configuration. type EffectiveTimeRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // StartTime is the time when the fault injection configuration starts to take effect. + // StartTime is the time when the fault injection configuration starts to take + // effect. StartTime string `protobuf:"bytes,1,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` - // EndTime is the time when the fault injection configuration ceases to be effective. + // EndTime is the time when the fault injection configuration ceases to be + // effective. EndTime string `protobuf:"bytes,2,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"` - // DaysOfWeek specifies which days of the week the fault injection configuration is effective. - // 0 represents Sunday, 1 represents Monday, and so on. + // DaysOfWeek specifies which days of the week the fault injection + // configuration is effective. 0 represents Sunday, 1 represents Monday, and + // so on. DaysOfWeek []int32 `protobuf:"varint,3,rep,packed,name=days_of_week,json=daysOfWeek,proto3" json:"days_of_week,omitempty"` - // DaysOfMonth specifies on which days of the month the fault injection configuration is effective. - // For example, 1 represents the first day of the month, and so on. + // DaysOfMonth specifies on which days of the month the fault injection + // configuration is effective. For example, 1 represents the first day of the + // month, and so on. DaysOfMonth []int32 `protobuf:"varint,4,rep,packed,name=days_of_month,json=daysOfMonth,proto3" json:"days_of_month,omitempty"` - // Months specifies which months of the year the fault injection configuration is effective. - // 1 represents January, 2 represents February, and so on. + // Months specifies which months of the year the fault injection configuration + // is effective. 1 represents January, 2 represents February, and so on. Months []int32 `protobuf:"varint,5,rep,packed,name=months,proto3" json:"months,omitempty"` } @@ -374,17 +425,18 @@ func (x *EffectiveTimeRange) GetMonths() []int32 { return nil } -type HttpMatch struct { +type Match struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Url []string `protobuf:"bytes,1,rep,name=url,proto3" json:"url,omitempty"` - Method []string `protobuf:"bytes,2,rep,name=method,proto3" json:"method,omitempty"` + Resources []*ResourceMatch `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"` + HttpMatch []*HttpMatch `protobuf:"bytes,2,rep,name=httpMatch,proto3" json:"httpMatch,omitempty"` + StringMatch []*StringMatch `protobuf:"bytes,3,rep,name=stringMatch,proto3" json:"stringMatch,omitempty"` } -func (x *HttpMatch) Reset() { - *x = HttpMatch{} +func (x *Match) Reset() { + *x = Match{} if protoimpl.UnsafeEnabled { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -392,13 +444,13 @@ func (x *HttpMatch) Reset() { } } -func (x *HttpMatch) String() string { +func (x *Match) String() string { return protoimpl.X.MessageStringOf(x) } -func (*HttpMatch) ProtoMessage() {} +func (*Match) ProtoMessage() {} -func (x *HttpMatch) ProtoReflect() protoreflect.Message { +func (x *Match) ProtoReflect() protoreflect.Message { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -410,36 +462,44 @@ func (x *HttpMatch) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use HttpMatch.ProtoReflect.Descriptor instead. -func (*HttpMatch) Descriptor() ([]byte, []int) { +// Deprecated: Use Match.ProtoReflect.Descriptor instead. +func (*Match) Descriptor() ([]byte, []int) { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{4} } -func (x *HttpMatch) GetUrl() []string { +func (x *Match) GetResources() []*ResourceMatch { if x != nil { - return x.Url + return x.Resources } return nil } -func (x *HttpMatch) GetMethod() []string { +func (x *Match) GetHttpMatch() []*HttpMatch { if x != nil { - return x.Method + return x.HttpMatch } return nil } -type Match struct { +func (x *Match) GetStringMatch() []*StringMatch { + if x != nil { + return x.StringMatch + } + return nil +} + +type StringMatch struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Resources []*ResourceMatch `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"` - HttpMatch []*HttpMatch `protobuf:"bytes,2,rep,name=httpMatch,proto3" json:"httpMatch,omitempty"` + MatchType StringMatch_StringMatchType `protobuf:"varint,1,opt,name=matchType,proto3,enum=proto.StringMatch_StringMatchType" json:"matchType,omitempty"` + Contents []string `protobuf:"bytes,2,rep,name=contents,proto3" json:"contents,omitempty"` + Methods []string `protobuf:"bytes,3,rep,name=methods,proto3" json:"methods,omitempty"` } -func (x *Match) Reset() { - *x = Match{} +func (x *StringMatch) Reset() { + *x = StringMatch{} if protoimpl.UnsafeEnabled { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -447,13 +507,13 @@ func (x *Match) Reset() { } } -func (x *Match) String() string { +func (x *StringMatch) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Match) ProtoMessage() {} +func (*StringMatch) ProtoMessage() {} -func (x *Match) ProtoReflect() protoreflect.Message { +func (x *StringMatch) ProtoReflect() protoreflect.Message { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -465,21 +525,83 @@ func (x *Match) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Match.ProtoReflect.Descriptor instead. -func (*Match) Descriptor() ([]byte, []int) { +// Deprecated: Use StringMatch.ProtoReflect.Descriptor instead. +func (*StringMatch) Descriptor() ([]byte, []int) { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{5} } -func (x *Match) GetResources() []*ResourceMatch { +func (x *StringMatch) GetMatchType() StringMatch_StringMatchType { if x != nil { - return x.Resources + return x.MatchType + } + return StringMatch_NORMAL +} + +func (x *StringMatch) GetContents() []string { + if x != nil { + return x.Contents } return nil } -func (x *Match) GetHttpMatch() []*HttpMatch { +func (x *StringMatch) GetMethods() []string { if x != nil { - return x.HttpMatch + return x.Methods + } + return nil +} + +type HttpMatch struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url []string `protobuf:"bytes,1,rep,name=url,proto3" json:"url,omitempty"` + Method []string `protobuf:"bytes,2,rep,name=method,proto3" json:"method,omitempty"` +} + +func (x *HttpMatch) Reset() { + *x = HttpMatch{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HttpMatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpMatch) ProtoMessage() {} + +func (x *HttpMatch) ProtoReflect() protoreflect.Message { + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpMatch.ProtoReflect.Descriptor instead. +func (*HttpMatch) Descriptor() ([]byte, []int) { + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{6} +} + +func (x *HttpMatch) GetUrl() []string { + if x != nil { + return x.Url + } + return nil +} + +func (x *HttpMatch) GetMethod() []string { + if x != nil { + return x.Method } return nil } @@ -521,7 +643,7 @@ type ResourceMatch struct { func (x *ResourceMatch) Reset() { *x = ResourceMatch{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -534,7 +656,7 @@ func (x *ResourceMatch) String() string { func (*ResourceMatch) ProtoMessage() {} func (x *ResourceMatch) ProtoReflect() protoreflect.Message { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -547,7 +669,7 @@ func (x *ResourceMatch) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceMatch.ProtoReflect.Descriptor instead. func (*ResourceMatch) Descriptor() ([]byte, []int) { - return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{6} + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{7} } func (x *ResourceMatch) GetApiGroups() []string { @@ -596,7 +718,7 @@ type HTTPFaultInjection_Delay struct { func (x *HTTPFaultInjection_Delay) Reset() { *x = HTTPFaultInjection_Delay{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -609,7 +731,7 @@ func (x *HTTPFaultInjection_Delay) String() string { func (*HTTPFaultInjection_Delay) ProtoMessage() {} func (x *HTTPFaultInjection_Delay) ProtoReflect() protoreflect.Message { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -675,7 +797,7 @@ type HTTPFaultInjection_Abort struct { func (x *HTTPFaultInjection_Abort) Reset() { *x = HTTPFaultInjection_Abort{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -688,7 +810,7 @@ func (x *HTTPFaultInjection_Abort) String() string { func (*HTTPFaultInjection_Abort) ProtoMessage() {} func (x *HTTPFaultInjection_Abort) ProtoReflect() protoreflect.Message { - mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -851,35 +973,49 @@ var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDesc = []byte{ 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x61, 0x79, 0x73, 0x4f, 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x73, - 0x22, 0x35, 0x0a, 0x09, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, - 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x6b, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, - 0x12, 0x32, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x48, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x09, 0x68, 0x74, 0x74, 0x70, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x65, 0x72, 0x62, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x65, 0x72, 0x62, 0x73, 0x32, 0x50, 0x0a, 0x0b, 0x46, 0x61, 0x75, 0x6c, - 0x74, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x53, 0x65, 0x6e, 0x64, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, - 0x75, 0x6c, 0x74, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1c, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4b, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2d, - 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x74, - 0x72, 0x6c, 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0xa1, 0x01, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x32, 0x0a, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, + 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x09, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x34, + 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x22, 0xb0, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x09, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0f, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, + 0x45, 0x47, 0x45, 0x58, 0x50, 0x10, 0x01, 0x22, 0x35, 0x0a, 0x09, 0x48, 0x74, 0x74, 0x70, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x81, + 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1e, + 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x1c, + 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x65, 0x72, 0x62, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x65, 0x72, + 0x62, 0x73, 0x32, 0x50, 0x0a, 0x0b, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6e, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6e, 0x6a, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, + 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x4b, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2d, 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x74, 0x72, 0x6c, 0x6d, 0x65, 0x73, 0x68, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -894,38 +1030,42 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP() []byte { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescData } -var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_goTypes = []interface{}{ (FaultInjection_Option)(0), // 0: proto.FaultInjection.Option - (*FaultInjectConfigResp)(nil), // 1: proto.FaultInjectConfigResp - (*FaultInjection)(nil), // 2: proto.FaultInjection - (*HTTPFaultInjection)(nil), // 3: proto.HTTPFaultInjection - (*EffectiveTimeRange)(nil), // 4: proto.EffectiveTimeRange - (*HttpMatch)(nil), // 5: proto.HttpMatch + (StringMatch_StringMatchType)(0), // 1: proto.StringMatch.StringMatchType + (*FaultInjectConfigResp)(nil), // 2: proto.FaultInjectConfigResp + (*FaultInjection)(nil), // 3: proto.FaultInjection + (*HTTPFaultInjection)(nil), // 4: proto.HTTPFaultInjection + (*EffectiveTimeRange)(nil), // 5: proto.EffectiveTimeRange (*Match)(nil), // 6: proto.Match - (*ResourceMatch)(nil), // 7: proto.ResourceMatch - (*HTTPFaultInjection_Delay)(nil), // 8: proto.HTTPFaultInjection.Delay - (*HTTPFaultInjection_Abort)(nil), // 9: proto.HTTPFaultInjection.Abort - (*durationpb.Duration)(nil), // 10: google.protobuf.Duration + (*StringMatch)(nil), // 7: proto.StringMatch + (*HttpMatch)(nil), // 8: proto.HttpMatch + (*ResourceMatch)(nil), // 9: proto.ResourceMatch + (*HTTPFaultInjection_Delay)(nil), // 10: proto.HTTPFaultInjection.Delay + (*HTTPFaultInjection_Abort)(nil), // 11: proto.HTTPFaultInjection.Abort + (*durationpb.Duration)(nil), // 12: google.protobuf.Duration } var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_depIdxs = []int32{ - 3, // 0: proto.FaultInjection.httpFaultInjections:type_name -> proto.HTTPFaultInjection + 4, // 0: proto.FaultInjection.httpFaultInjections:type_name -> proto.HTTPFaultInjection 0, // 1: proto.FaultInjection.option:type_name -> proto.FaultInjection.Option - 8, // 2: proto.HTTPFaultInjection.delay:type_name -> proto.HTTPFaultInjection.Delay - 9, // 3: proto.HTTPFaultInjection.abort:type_name -> proto.HTTPFaultInjection.Abort + 10, // 2: proto.HTTPFaultInjection.delay:type_name -> proto.HTTPFaultInjection.Delay + 11, // 3: proto.HTTPFaultInjection.abort:type_name -> proto.HTTPFaultInjection.Abort 6, // 4: proto.HTTPFaultInjection.match:type_name -> proto.Match - 4, // 5: proto.HTTPFaultInjection.effective_time:type_name -> proto.EffectiveTimeRange - 7, // 6: proto.Match.resources:type_name -> proto.ResourceMatch - 5, // 7: proto.Match.httpMatch:type_name -> proto.HttpMatch - 10, // 8: proto.HTTPFaultInjection.Delay.fixed_delay:type_name -> google.protobuf.Duration - 2, // 9: proto.FaultInject.SendConfig:input_type -> proto.FaultInjection - 1, // 10: proto.FaultInject.SendConfig:output_type -> proto.FaultInjectConfigResp - 10, // [10:11] is the sub-list for method output_type - 9, // [9:10] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 5, // 5: proto.HTTPFaultInjection.effective_time:type_name -> proto.EffectiveTimeRange + 9, // 6: proto.Match.resources:type_name -> proto.ResourceMatch + 8, // 7: proto.Match.httpMatch:type_name -> proto.HttpMatch + 7, // 8: proto.Match.stringMatch:type_name -> proto.StringMatch + 1, // 9: proto.StringMatch.matchType:type_name -> proto.StringMatch.StringMatchType + 12, // 10: proto.HTTPFaultInjection.Delay.fixed_delay:type_name -> google.protobuf.Duration + 3, // 11: proto.FaultInject.SendConfig:input_type -> proto.FaultInjection + 2, // 12: proto.FaultInject.SendConfig:output_type -> proto.FaultInjectConfigResp + 12, // [12:13] is the sub-list for method output_type + 11, // [11:12] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() } @@ -983,7 +1123,7 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HttpMatch); i { + switch v := v.(*Match); i { case 0: return &v.state case 1: @@ -995,7 +1135,7 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Match); i { + switch v := v.(*StringMatch); i { case 0: return &v.state case 1: @@ -1007,7 +1147,7 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResourceMatch); i { + switch v := v.(*HttpMatch); i { case 0: return &v.state case 1: @@ -1019,7 +1159,7 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTTPFaultInjection_Delay); i { + switch v := v.(*ResourceMatch); i { case 0: return &v.state case 1: @@ -1031,6 +1171,18 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HTTPFaultInjection_Delay); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HTTPFaultInjection_Abort); i { case 0: return &v.state @@ -1043,10 +1195,10 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } } - file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7].OneofWrappers = []interface{}{ + file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8].OneofWrappers = []interface{}{ (*HTTPFaultInjection_Delay_FixedDelay)(nil), } - file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8].OneofWrappers = []interface{}{ + file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9].OneofWrappers = []interface{}{ (*HTTPFaultInjection_Abort_HttpStatus)(nil), (*HTTPFaultInjection_Abort_GrpcStatus)(nil), (*HTTPFaultInjection_Abort_Http2Error)(nil), @@ -1056,8 +1208,8 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDesc, - NumEnums: 1, - NumMessages: 9, + NumEnums: 2, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/apis/ctrlmesh/proto/faultinjection.proto b/pkg/apis/ctrlmesh/proto/faultinjection.proto index 5858405..8d79e6b 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection.proto +++ b/pkg/apis/ctrlmesh/proto/faultinjection.proto @@ -55,7 +55,7 @@ message HTTPFaultInjection { oneof http_delay_type { // Add a fixed delay before forwarding the request. Format: // 1h/1m/1s/1ms. MUST be >=1ms. - google.protobuf.Duration fixed_delay = 2[(google.api.field_behavior) = REQUIRED]; + google.protobuf.Duration fixed_delay = 2 [ (google.api.field_behavior) = REQUIRED ]; // google.protobuf.Duration exponential_delay = 3 ; } @@ -82,39 +82,53 @@ message HTTPFaultInjection { } } -// EffectiveTimeRange specifies the effective time range for fault injection configuration. +// EffectiveTimeRange specifies the effective time range for fault injection +// configuration. message EffectiveTimeRange { - // StartTime is the time when the fault injection configuration starts to take effect. + // StartTime is the time when the fault injection configuration starts to take + // effect. string start_time = 1; - // EndTime is the time when the fault injection configuration ceases to be effective. + // EndTime is the time when the fault injection configuration ceases to be + // effective. string end_time = 2; - // DaysOfWeek specifies which days of the week the fault injection configuration is effective. - // 0 represents Sunday, 1 represents Monday, and so on. + // DaysOfWeek specifies which days of the week the fault injection + // configuration is effective. 0 represents Sunday, 1 represents Monday, and + // so on. repeated int32 days_of_week = 3; - // DaysOfMonth specifies on which days of the month the fault injection configuration is effective. - // For example, 1 represents the first day of the month, and so on. + // DaysOfMonth specifies on which days of the month the fault injection + // configuration is effective. For example, 1 represents the first day of the + // month, and so on. repeated int32 days_of_month = 4; - // Months specifies which months of the year the fault injection configuration is effective. - // 1 represents January, 2 represents February, and so on. + // Months specifies which months of the year the fault injection configuration + // is effective. 1 represents January, 2 represents February, and so on. repeated int32 months = 5; } +message Match { + repeated ResourceMatch resources = 1; + repeated HttpMatch httpMatch = 2; + repeated StringMatch stringMatch = 3; +} +message StringMatch { + StringMatchType matchType = 1; + repeated string contents = 2; + repeated string methods = 3; + enum StringMatchType { + NORMAL = 0; + REGEXP = 1; + } +} message HttpMatch { repeated string url = 1; repeated string method = 2; } -message Match { - repeated ResourceMatch resources = 1; - repeated HttpMatch httpMatch = 2; -} - // Describes how to match K8s resources. // // ```yaml diff --git a/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go b/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go index 2d3ed46..a28efc2 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go +++ b/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go @@ -131,45 +131,66 @@ func (in *EffectiveTimeRange) DeepCopyInterface() interface{} { return in.DeepCopy() } -// DeepCopyInto supports using HttpMatch within kubernetes types, where deepcopy-gen is used. -func (in *HttpMatch) DeepCopyInto(out *HttpMatch) { - p := proto.Clone(in).(*HttpMatch) +// DeepCopyInto supports using Match within kubernetes types, where deepcopy-gen is used. +func (in *Match) DeepCopyInto(out *Match) { + p := proto.Clone(in).(*Match) *out = *p } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpMatch. Required by controller-gen. -func (in *HttpMatch) DeepCopy() *HttpMatch { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. Required by controller-gen. +func (in *Match) DeepCopy() *Match { if in == nil { return nil } - out := new(HttpMatch) + out := new(Match) in.DeepCopyInto(out) return out } -// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new HttpMatch. Required by controller-gen. -func (in *HttpMatch) DeepCopyInterface() interface{} { +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Match. Required by controller-gen. +func (in *Match) DeepCopyInterface() interface{} { return in.DeepCopy() } -// DeepCopyInto supports using Match within kubernetes types, where deepcopy-gen is used. -func (in *Match) DeepCopyInto(out *Match) { - p := proto.Clone(in).(*Match) +// DeepCopyInto supports using StringMatch within kubernetes types, where deepcopy-gen is used. +func (in *StringMatch) DeepCopyInto(out *StringMatch) { + p := proto.Clone(in).(*StringMatch) *out = *p } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. Required by controller-gen. -func (in *Match) DeepCopy() *Match { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. Required by controller-gen. +func (in *StringMatch) DeepCopy() *StringMatch { if in == nil { return nil } - out := new(Match) + out := new(StringMatch) in.DeepCopyInto(out) return out } -// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Match. Required by controller-gen. -func (in *Match) DeepCopyInterface() interface{} { +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. Required by controller-gen. +func (in *StringMatch) DeepCopyInterface() interface{} { + return in.DeepCopy() +} + +// DeepCopyInto supports using HttpMatch within kubernetes types, where deepcopy-gen is used. +func (in *HttpMatch) DeepCopyInto(out *HttpMatch) { + p := proto.Clone(in).(*HttpMatch) + *out = *p +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpMatch. Required by controller-gen. +func (in *HttpMatch) DeepCopy() *HttpMatch { + if in == nil { + return nil + } + out := new(HttpMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new HttpMatch. Required by controller-gen. +func (in *HttpMatch) DeepCopyInterface() interface{} { return in.DeepCopy() } diff --git a/pkg/apis/ctrlmesh/utils/conv/faultconv.go b/pkg/apis/ctrlmesh/utils/conv/faultconv.go index d5780c1..bd3f5e1 100644 --- a/pkg/apis/ctrlmesh/utils/conv/faultconv.go +++ b/pkg/apis/ctrlmesh/utils/conv/faultconv.go @@ -104,10 +104,14 @@ func ConvertHTTPMatch(match *ctrlmeshv1alpha1.Match) *ctrlmeshproto.Match { if match.Resources != nil { Match.Resources = make([]*ctrlmeshproto.ResourceMatch, len(match.Resources)) for i, relatedResource := range match.Resources { - Match.Resources[i] = ConvertRelatedResources(relatedResource) } - + } + if match.ContentMatch != nil { + Match.StringMatch = make([]*ctrlmeshproto.StringMatch, len(match.ContentMatch)) + for i, stringMatch := range match.ContentMatch { + Match.StringMatch[i] = ConvertStringMatch(stringMatch) + } } return Match } @@ -160,3 +164,25 @@ func ConvertRelatedResources(resourceRule *ctrlmeshv1alpha1.ResourceMatch) *ctrl } return protoResourceRule } + +func ConvertStringMatch(stringMatch *ctrlmeshv1alpha1.StringMatch) *ctrlmeshproto.StringMatch { + res := &ctrlmeshproto.StringMatch{} + if stringMatch != nil { + if stringMatch.MatchType == ctrlmeshv1alpha1.StringMatchTypeNormal { + res.MatchType = ctrlmeshproto.StringMatch_NORMAL + } else if stringMatch.MatchType == ctrlmeshv1alpha1.StringMatchTypeRegexp { + res.MatchType = ctrlmeshproto.StringMatch_REGEXP + } else { + return res + } + if stringMatch.Contents != nil { + res.Contents = make([]string, len(stringMatch.Contents)) + copy(res.Contents, stringMatch.Contents) + } + if stringMatch.Methods != nil { + res.Methods = make([]string, len(stringMatch.Methods)) + copy(res.Methods, stringMatch.Methods) + } + } + return res +} diff --git a/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go b/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go index 4672f47..bac0702 100644 --- a/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go +++ b/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go @@ -20,13 +20,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type StringMatch struct { - MatchType StringMatchType `json:"matchType,omitempty"` - Value string `json:"value,omitempty"` -} - type StringMatchType string +const ( + StringMatchTypeNormal StringMatchType = "Normal" + StringMatchTypeRegexp StringMatchType = "Regexp" +) + type HTTPFaultInjectionDelay struct { // FixedDelay is used to indicate the amount of delay in seconds. FixedDelay string `json:"fixedDelay,omitempty"` @@ -60,12 +60,22 @@ type HttpMatch struct { Method []string `json:"method"` } +type StringMatch struct { + MatchType StringMatchType `json:"matchType,omitempty"` + // Content is the content of the fault injection rule + Contents []string `json:"contents"` + // Method specifies the http method of the request, like: PUT, POST, GET, DELETE. + Methods []string `json:"methods"` +} + // Match defines a set of rules and criteria for matching incoming HTTP requests. // It associates a name with the set of criteria and can relate to additional resources that are relevant // to the request matching, enabling complex and granular control over request matching in HTTP FaultInjection. type Match struct { Resources []*ResourceMatch `json:"resources,omitempty"` HttpMatch []*HttpMatch `json:"httpMatch,omitempty"` + // ContentMatch + ContentMatch []*StringMatch `json:"contentMatch,omitempty"` } // HTTPFaultInjection can be used to specify one or more faults to inject diff --git a/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go index 13eb73f..2acc2e8 100644 --- a/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go @@ -728,6 +728,17 @@ func (in *Match) DeepCopyInto(out *Match) { } } } + if in.ContentMatch != nil { + in, out := &in.ContentMatch, &out.ContentMatch + *out = make([]*StringMatch, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(StringMatch) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. @@ -1124,6 +1135,16 @@ func (in *ShardingConfigWebhookConfiguration) DeepCopy() *ShardingConfigWebhookC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StringMatch) DeepCopyInto(out *StringMatch) { *out = *in + if in.Contents != nil { + in, out := &in.Contents, &out.Contents + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. diff --git a/pkg/manager/controllers/circuitbreaker/filter.go b/pkg/manager/controllers/circuitbreaker/filter.go index 9db57d2..d20d096 100644 --- a/pkg/manager/controllers/circuitbreaker/filter.go +++ b/pkg/manager/controllers/circuitbreaker/filter.go @@ -44,11 +44,18 @@ func (b *BreakerPredicate) Update(e event.UpdateEvent) bool { if newCB.DeletionTimestamp != nil || len(oldCB.Finalizers) != len(newCB.Finalizers) { return true } + oldValue, oldExists := "", false + newValue, newExists := "", false + + if oldCB.Labels != nil { + oldValue, oldExists = oldCB.Labels[ctrlmesh.CtrlmeshCircuitBreakerDisableKey] + } if newCB.Labels != nil { - _, ok := newCB.Labels[ctrlmesh.CtrlmeshCircuitBreakerDisableKey] - if ok { - return true - } + newValue, newExists = newCB.Labels[ctrlmesh.CtrlmeshCircuitBreakerDisableKey] + } + + if (oldExists != newExists) || (oldExists && newExists && oldValue != newValue) { + return true } oldProtoCB := conv.ConvertCircuitBreaker(oldCB) newProtoCB := conv.ConvertCircuitBreaker(newCB) diff --git a/pkg/proxy/apiserver/handler.go b/pkg/proxy/apiserver/handler.go index e2768bb..bc2109c 100644 --- a/pkg/proxy/apiserver/handler.go +++ b/pkg/proxy/apiserver/handler.go @@ -53,7 +53,7 @@ var ( enableIpTable = os.Getenv(constants.EnvIPTable) == "true" disableCircuitBreaker = os.Getenv(constants.EnvDisableCircuitBreaker) == "true" - disableFaultInjection = os.Getenv(constants.EnvDisableCircuitBreaker) == "true" + enableFaultInjection = os.Getenv(constants.EnvEnableFaultInjection) == "true" ) type Proxy struct { @@ -91,7 +91,7 @@ func NewProxy(opts *Options) (*Proxy, error) { if opts.BreakerWrapperFunc != nil && !disableCircuitBreaker { handler = opts.BreakerWrapperFunc(handler) } - if opts.FaultInjectionWrapperFunc != nil && !disableFaultInjection { + if opts.FaultInjectionWrapperFunc != nil && enableFaultInjection { handler = opts.FaultInjectionWrapperFunc(handler) } handler = genericfilters.WithWaitGroup(handler, opts.LongRunningFunc, opts.HandlerChainWaitGroup) diff --git a/pkg/proxy/circuitbreaker/manager.go b/pkg/proxy/circuitbreaker/manager.go index 5df7aa9..49cfa8c 100644 --- a/pkg/proxy/circuitbreaker/manager.go +++ b/pkg/proxy/circuitbreaker/manager.go @@ -79,6 +79,9 @@ func (m *manager) Sync(config *ctrlmeshproto.CircuitBreaker) (*ctrlmeshproto.Con LimitingSnapshot: m.snapshot(config.Name), }, nil } else { + if ok { + m.unregisterRules(cb.Name) + } m.breakerMap[config.Name] = config m.registerRules(config) var msg string @@ -163,9 +166,6 @@ type ValidateResult struct { // RegisterRules register a circuit breaker to the local limiter store func (m *manager) registerRules(cb *ctrlmeshproto.CircuitBreaker) { logger.Info("register rule", "circuit-breaker", cb.Name) - if _, ok := m.breakerMap[cb.Name]; ok { - m.unregisterRules(cb.Name) - } for _, limiting := range cb.RateLimitings { key := fmt.Sprintf("%s:%s", cb.Name, limiting.Name) diff --git a/pkg/proxy/faultinjection/manager.go b/pkg/proxy/faultinjection/manager.go index e8c3faf..a984ff8 100644 --- a/pkg/proxy/faultinjection/manager.go +++ b/pkg/proxy/faultinjection/manager.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "math/rand" "net/http" "net/url" @@ -30,9 +31,7 @@ import ( "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" apirequest "k8s.io/apiserver/pkg/endpoints/request" - logf "sigs.k8s.io/controller-runtime/pkg/log" - - // pkgfi "github.com/KusionStack/controller-mesh/circuitbreaker" + "k8s.io/klog/v2" ctrlmeshproto "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/proto" "github.com/KusionStack/controller-mesh/pkg/utils" @@ -41,7 +40,6 @@ import ( const timeLayout = "15:04:05" var ( - logger = logf.Log.WithName("fault-injection-manager") randNum = rand.New(rand.NewSource(time.Now().UnixNano())) ) @@ -59,6 +57,7 @@ type FaultInjectionResult struct { type FaultInjector interface { FaultInjectionRest(URL string, method string) (result *FaultInjectionResult) + FaultInjectionNormalOrRegexp(URL string, method string) (result *FaultInjectionResult) FaultInjectionResource(namespace, apiGroup, resource, verb string) (result *FaultInjectionResult) HandlerWrapper() func(http.Handler) http.Handler } @@ -89,6 +88,9 @@ func (m *manager) Sync(config *ctrlmeshproto.FaultInjection) (*ctrlmeshproto.Fau Message: fmt.Sprintf("faultInjection spec hash not updated, hash %s", fi.ConfigHash), }, nil } else { + if ok { + m.unregisterRules(fi.Name) + } m.faultInjectionMap[config.Name] = config m.registerRules(config) var msg string @@ -117,16 +119,16 @@ func (m *manager) Sync(config *ctrlmeshproto.FaultInjection) (*ctrlmeshproto.Fau }, nil } case ctrlmeshproto.FaultInjection_CHECK: - cb, ok := m.faultInjectionMap[config.Name] + fi, ok := m.faultInjectionMap[config.Name] if !ok { return &ctrlmeshproto.FaultInjectConfigResp{ Success: false, - Message: fmt.Sprintf("fault injection config %s not found", cb.Name), + Message: fmt.Sprintf("fault injection config %s not found", fi.Name), }, nil - } else if config.ConfigHash != cb.ConfigHash { + } else if config.ConfigHash != fi.ConfigHash { return &ctrlmeshproto.FaultInjectConfigResp{ Success: false, - Message: fmt.Sprintf("unequal fault injection %s hash, old %s, new %s", cb.Name, cb.ConfigHash, config.ConfigHash), + Message: fmt.Sprintf("unequal fault injection %s hash, old %s, new %s", fi.Name, fi.ConfigHash, config.ConfigHash), }, nil } return &ctrlmeshproto.FaultInjectConfigResp{ @@ -148,19 +150,48 @@ func (m *manager) HandlerWrapper() func(http.Handler) http.Handler { } } -func (m *manager) FaultInjectionRest(URL string, method string) (result *FaultInjectionResult) { +func (m *manager) FaultInjectionNormalOrRegexp(URL string, method string) (result *FaultInjectionResult) { + // regexp and normal now := time.Now() - defer func() { - logger.Info("validate rest", "URL", URL, "method", method, "result", result, "cost time", time.Since(now).String()) - }() + indexes := m.faultInjectionStore.normalIndexes[indexForRest(URL, method)] + if indexes == nil { + indexes = m.faultInjectionStore.normalIndexes[indexForRest(URL, "*")] + } + for key := range indexes { + faultInjection := m.faultInjectionStore.rules[key] + if faultInjection != nil { + result = m.doFaultInjection([]*ctrlmeshproto.HTTPFaultInjection{faultInjection}) + klog.Infof("validate rest, URL: %s, method:%s, result: %v, cost time: %v ", URL, method, result, time.Since(now).String()) + return result + } + } + for key, regs := range m.faultInjectionStore.regexpIndexes { + for _, reg := range regs { + if reg.method == method && reg.reg.MatchString(URL) { + faultInjection := m.faultInjectionStore.rules[key] + if faultInjection != nil { + result = m.doFaultInjection([]*ctrlmeshproto.HTTPFaultInjection{faultInjection}) + klog.Infof("validate rest, URL: %s, method:%s, result: %v, cost time: %v ", URL, method, result, time.Since(now).String()) + return result + } + } + } + } + result = &FaultInjectionResult{Abort: false, Reason: "No rule match"} + return result +} + +func (m *manager) FaultInjectionRest(URL string, method string) (result *FaultInjectionResult) { + now := time.Now() urls := generateWildcardUrls(URL, method) for _, url := range urls { - faultInjections, states := m.faultInjectionStore.byIndex(IndexRest, url) + faultInjections, _ := m.faultInjectionStore.byIndex(IndexRest, url) if len(faultInjections) == 0 { continue } - result = m.doFaultInjection(faultInjections, states) + result = m.doFaultInjection(faultInjections) + klog.Infof("validate rest, URL: %s, method:%s, result: %v, cost time: %v ", URL, method, result, time.Since(now).String()) return result } result = &FaultInjectionResult{Abort: false, Reason: "No rule match"} @@ -173,7 +204,7 @@ func generateWildcardUrls(URL string, method string) []string { URL = strings.TrimSuffix(URL, "/") u, err := url.Parse(URL) if err != nil { - logger.Error(err, "failed to url", "URL", URL, "method", method) + klog.Errorf("failed to url, URL: %s, method: %s,err: %v", URL, method, err) return result } if len(u.Path) > 0 { @@ -189,16 +220,14 @@ func generateWildcardUrls(URL string, method string) []string { func (m *manager) FaultInjectionResource(namespace, apiGroup, resource, verb string) (result *FaultInjectionResult) { now := time.Now() - defer func() { - logger.Info("validate resource", "namespace", namespace, "apiGroup", apiGroup, "resource", resource, "verb", verb, "result", result, "cost time", time.Since(now).String()) - }() seeds := generateWildcardSeeds(namespace, apiGroup, resource, verb) for _, seed := range seeds { - faultInjections, states := m.faultInjectionStore.byIndex(IndexResource, seed) + faultInjections, _ := m.faultInjectionStore.byIndex(IndexResource, seed) if len(faultInjections) == 0 { continue } - result = m.doFaultInjection(faultInjections, states) + result = m.doFaultInjection(faultInjections) + klog.Infof("validate resource, namespace: %s, apiGroup: %s, resource: %s, verb: %s, result: %v, cost time: %v, ", namespace, apiGroup, resource, verb, result, time.Since(now).String()) return result } result = &FaultInjectionResult{Abort: false, Reason: "No rule match"} @@ -253,8 +282,12 @@ func withFaultInjection(injector FaultInjector, handler http.Handler) http.Handl if apiErr.Code != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(int(apiErr.Code)) - json.NewEncoder(w).Encode(apiErr) - logger.Info("faultinjection rule", fmt.Sprintf("fault injection, %s, %s,%d", result.Reason, result.Message, result.ErrCode)) + if err := json.NewEncoder(w).Encode(apiErr); err != nil { + // Error encoding the JSON response, at this point the headers are already written. + klog.Errorf("failed to write api error response: %v", err) + return + } + klog.Infof("faultinjection rule: %s", fmt.Sprintf("fault injection, %s, %s,%d", apiErr.Reason, apiErr.Message, apiErr.Code)) return } } @@ -262,7 +295,7 @@ func withFaultInjection(injector FaultInjector, handler http.Handler) http.Handl }) } -func (m *manager) doFaultInjection(faultInjections []*ctrlmeshproto.HTTPFaultInjection, states []*state) *FaultInjectionResult { +func (m *manager) doFaultInjection(faultInjections []*ctrlmeshproto.HTTPFaultInjection) *FaultInjectionResult { result := &FaultInjectionResult{ Abort: false, Reason: "Default allow", @@ -278,7 +311,7 @@ func (m *manager) doFaultInjection(faultInjections []*ctrlmeshproto.HTTPFaultInj if isInpercentRange(faultInjections[idx].Delay.Percent) { delay := faultInjections[idx].Delay.GetFixedDelay() delayDuration := delay.AsDuration() - logger.Info("Delaying time ", "for", delayDuration) + klog.Infof("Delaying time: %v ", delayDuration) time.Sleep(delayDuration) } } @@ -288,6 +321,7 @@ func (m *manager) doFaultInjection(faultInjections []*ctrlmeshproto.HTTPFaultInj result.Reason = "FaultInjectionTriggered" result.Message = fmt.Sprintf("the fault injection is triggered. Limiting rule name: %s", faultInjections[idx].Name) result.ErrCode = faultInjections[idx].Abort.GetHttpStatus() + return result } } @@ -398,11 +432,7 @@ func isEffectiveTimeRange(timeRange *ctrlmeshproto.EffectiveTimeRange) bool { // RegisterRules register a fault injection to the local store func (m *manager) registerRules(fi *ctrlmeshproto.FaultInjection) { - logger.Info("register rule", "faultInjection", fi.Name) - if _, ok := m.faultInjectionMap[fi.Name]; ok { - m.unregisterRules(fi.Name) - } - + klog.Infof("register rule, faultInjection: %s", fi.Name) for _, faultInjection := range fi.HttpFaultInjections { key := fmt.Sprintf("%s:%s", fi.Name, faultInjection.Name) m.faultInjectionStore.createOrUpdateRule( @@ -413,7 +443,7 @@ func (m *manager) registerRules(fi *ctrlmeshproto.FaultInjection) { // UnregisterRules unregister a fault injection to the local store func (m *manager) unregisterRules(fiName string) { - logger.Info("unregister rule", "faultInjection", fiName) + klog.Infof("unregister rule, faultInjection: %s", fiName) fi, ok := m.faultInjectionMap[fiName] if !ok { return diff --git a/pkg/proxy/faultinjection/manager_test.go b/pkg/proxy/faultinjection/manager_test.go index bed1a83..9090223 100644 --- a/pkg/proxy/faultinjection/manager_test.go +++ b/pkg/proxy/faultinjection/manager_test.go @@ -17,6 +17,7 @@ limitations under the License. package faultinjection import ( + "context" "fmt" "os" "testing" @@ -26,15 +27,275 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" ctrlmeshproto "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/proto" + "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/utils/conv" + ctrlmeshv1alpha1 "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func init() { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) } -func TestRestTrafficIntercept(t *testing.T) { +func TestStringMatch(t *testing.T) { fmt.Println("TestRestTrafficIntercept") + fi1 := &ctrlmeshv1alpha1.FaultInjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName1", + Namespace: "default", + Annotations: map[string]string{ + "test": "test", + }, + Labels: map[string]string{}, + }, + Spec: ctrlmeshv1alpha1.FaultInjectionSpec{ + + HTTPFaultInjections: []*ctrlmeshv1alpha1.HTTPFaultInjection{ + { + Name: "deletePod", + Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ + HttpStatus: 409, + Percent: "100", + }, + Match: &ctrlmeshv1alpha1.Match{ + Resources: []*ctrlmeshv1alpha1.ResourceMatch{ + { + ApiGroups: []string{"*"}, + Namespaces: []string{"*"}, + Verbs: []string{"delete"}, + Resources: []string{"pod"}, + }, + }, + ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + { + MatchType: ctrlmeshv1alpha1.StringMatchTypeNormal, + Contents: []string{"www.hello.com", "www.testa.com"}, + Methods: []string{"POST", "GET"}, + }, + }, + }, + }, + }, + }, + } + g := gomega.NewGomegaWithT(t) + mgr := NewManager(context.TODO()) + protoFault := conv.ConvertFaultInjection(fi1) + protoFault.Option = ctrlmeshproto.FaultInjection_UPDATE + _, err := mgr.Sync(protoFault) + g.Expect(err).Should(gomega.BeNil()) + + // test limit delete pod + + result := mgr.FaultInjectionNormalOrRegexp("www.hello.com", "POST") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.hello.com", "GET") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.hello.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeFalse()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com", "GET") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com", "POST") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com", "PUT") + g.Expect(result.Abort).To(gomega.BeFalse()) + + fi2 := &ctrlmeshv1alpha1.FaultInjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName2", + Namespace: "default", + Annotations: map[string]string{ + "test": "test", + }, + Labels: map[string]string{}, + }, + Spec: ctrlmeshv1alpha1.FaultInjectionSpec{ + HTTPFaultInjections: []*ctrlmeshv1alpha1.HTTPFaultInjection{ + { + Name: "rule2", + Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ + HttpStatus: 409, + Percent: "100", + }, + Match: &ctrlmeshv1alpha1.Match{ + Resources: []*ctrlmeshv1alpha1.ResourceMatch{ + { + ApiGroups: []string{"*"}, + Namespaces: []string{"*"}, + Verbs: []string{"delete"}, + Resources: []string{"pod"}, + }, + }, + ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + { + MatchType: ctrlmeshv1alpha1.StringMatchTypeNormal, + Contents: []string{"www.hello.com", "www.testa.com"}, + Methods: []string{"*", "GET"}, + }, + }, + }, + }, + }, + }, + } + + protoFault2 := conv.ConvertFaultInjection(fi2) + protoFault2.Option = ctrlmeshproto.FaultInjection_UPDATE + _, err = mgr.Sync(protoFault2) + g.Expect(err).Should(gomega.BeNil()) + + result = mgr.FaultInjectionNormalOrRegexp("www.hello.com", "GET") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.hello.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com1", "DELETE") + g.Expect(result.Abort).To(gomega.BeFalse()) + + fi3 := &ctrlmeshv1alpha1.FaultInjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName3", + Namespace: "default", + Annotations: map[string]string{ + "test": "test", + }, + Labels: map[string]string{}, + }, + Spec: ctrlmeshv1alpha1.FaultInjectionSpec{ + HTTPFaultInjections: []*ctrlmeshv1alpha1.HTTPFaultInjection{ + { + Name: "rule3", + Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ + HttpStatus: 409, + Percent: "100", + }, + Match: &ctrlmeshv1alpha1.Match{ + Resources: []*ctrlmeshv1alpha1.ResourceMatch{ + { + ApiGroups: []string{"*"}, + Namespaces: []string{"*"}, + Verbs: []string{"delete"}, + Resources: []string{"pod"}, + }, + }, + ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + { + MatchType: ctrlmeshv1alpha1.StringMatchTypeNormal, + Contents: []string{"www.hello.com", "www.testa.com"}, + Methods: []string{"DELETE"}, + }, + }, + }, + }, + }, + }, + } + + protoFault3 := conv.ConvertFaultInjection(fi3) + protoFault3.Option = ctrlmeshproto.FaultInjection_UPDATE + _, err = mgr.Sync(protoFault3) + g.Expect(err).Should(gomega.BeNil()) + result = mgr.FaultInjectionNormalOrRegexp("www.hello.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.testa.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + + fi4 := &ctrlmeshv1alpha1.FaultInjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName4", + Namespace: "default", + Annotations: map[string]string{ + "test": "test", + }, + Labels: map[string]string{}, + }, + Spec: ctrlmeshv1alpha1.FaultInjectionSpec{ + HTTPFaultInjections: []*ctrlmeshv1alpha1.HTTPFaultInjection{ + { + Name: "rule4", + Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ + HttpStatus: 409, + Percent: "100", + }, + Match: &ctrlmeshv1alpha1.Match{ + Resources: []*ctrlmeshv1alpha1.ResourceMatch{ + { + ApiGroups: []string{"*"}, + Namespaces: []string{"*"}, + Verbs: []string{"delete"}, + Resources: []string{"pod"}, + }, + }, + ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + { + MatchType: ctrlmeshv1alpha1.StringMatchTypeRegexp, + Contents: []string{"(ali[a-z]+)"}, + Methods: []string{"DELETE"}, + }, + }, + }, + }, + }, + }, + } + protoFault4 := conv.ConvertFaultInjection(fi4) + _, err = mgr.Sync(protoFault4) + g.Expect(err).Should(gomega.BeNil()) + + result = mgr.FaultInjectionNormalOrRegexp("alia", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + result = mgr.FaultInjectionNormalOrRegexp("www.alibab.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + g.Expect(result.Reason).To(gomega.BeEquivalentTo("FaultInjectionTriggered")) + + fi5 := &ctrlmeshv1alpha1.FaultInjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName5", + Namespace: "default", + Annotations: map[string]string{ + "test": "test", + }, + Labels: map[string]string{}, + }, + Spec: ctrlmeshv1alpha1.FaultInjectionSpec{ + HTTPFaultInjections: []*ctrlmeshv1alpha1.HTTPFaultInjection{ + { + Name: "rule5", + Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ + HttpStatus: 409, + Percent: "100", + }, + Match: &ctrlmeshv1alpha1.Match{ + Resources: []*ctrlmeshv1alpha1.ResourceMatch{ + { + ApiGroups: []string{"*"}, + Namespaces: []string{"*"}, + Verbs: []string{"delete"}, + Resources: []string{"pod"}, + }, + }, + ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + { + MatchType: ctrlmeshv1alpha1.StringMatchTypeRegexp, + Contents: []string{"(bai[a-z]+)"}, + Methods: []string{"DELETE"}, + }, + }, + }, + }, + }, + }, + } + protoFault5 := conv.ConvertFaultInjection(fi5) + _, err = mgr.Sync(protoFault5) + g.Expect(err).Should(gomega.BeNil()) + + result = mgr.FaultInjectionNormalOrRegexp("baid.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) + g.Expect(result.Message).To(gomega.ContainSubstring("rule5")) + result = mgr.FaultInjectionNormalOrRegexp("www.baida.com", "DELETE") + g.Expect(result.Abort).To(gomega.BeTrue()) } func TestIsEffectiveTimeRange(t *testing.T) { diff --git a/pkg/proxy/faultinjection/store.go b/pkg/proxy/faultinjection/store.go index ed7e37a..64bd60f 100644 --- a/pkg/proxy/faultinjection/store.go +++ b/pkg/proxy/faultinjection/store.go @@ -19,10 +19,12 @@ package faultinjection import ( "context" "fmt" + "regexp" "sync" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" ctrlmeshproto "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/proto" ) @@ -58,13 +60,26 @@ func (s *state) read() (lastTime *metav1.Time) { return } +// regexpInfo is contain regexp info +type regexpInfo struct { + // reg is after regexp compiled result + reg *regexp.Regexp + + // method is represent url request method + method string +} + // faultInjectionStore is a thread-safe local store for faultinjection rules type store struct { mu sync.RWMutex - // rule cache, key is {cb.namespace}:{cb.name}:{rule.name} + // rule cache, key is {fi.namespace}:{fi.name}:{fi.name} rules map[string]*ctrlmeshproto.HTTPFaultInjection // indices: resource indices and rest indices indices indices + // normalIndex cache, key is {faultinjection.Content}:{faultinjection.Method}, value is {fi.namespace}:{fi.name}:{rule.name} + normalIndexes map[string]sets.Set[string] + // regexpIndex cache, key is {fi.namespace}:{fi.name}:{rule.name}, value is regexpInfo + regexpIndexes map[string][]*regexpInfo faultInjectionLease *lease @@ -77,6 +92,8 @@ func newFaultInjectionStore(ctx context.Context) *store { indices: indices{}, faultInjectionLease: newFaultInjectionLease(ctx), ctx: ctx, + normalIndexes: make(map[string]sets.Set[string]), + regexpIndexes: make(map[string][]*regexpInfo), } return s } @@ -152,6 +169,34 @@ func (s *store) updateIndices(oldOne, newOne *ctrlmeshproto.HTTPFaultInjection, set.Insert(key) } } + + for _, newStringMatch := range newOne.Match.StringMatch { + if newStringMatch.MatchType == ctrlmeshproto.StringMatch_NORMAL { + for _, content := range newStringMatch.Contents { + for _, method := range newStringMatch.Methods { + urlMethod := indexForRest(content, method) + set := s.normalIndexes[urlMethod] + if set == nil { + set = sets.New[string]() + s.normalIndexes[urlMethod] = set + } + set.Insert(key) + } + } + } else if newStringMatch.MatchType == ctrlmeshproto.StringMatch_REGEXP { + for _, content := range newStringMatch.Contents { + if reg, err := regexp.Compile(content); err != nil { + klog.Error("Regexp compile with error %v", err) + } else { + for _, method := range newStringMatch.Methods { + s.regexpIndexes[key] = append(s.regexpIndexes[key], ®expInfo{reg: reg, method: method}) + } + } + } + } + + } + } // deleteFromIndices deletes indices of specified keys @@ -172,6 +217,27 @@ func (s *store) deleteFromIndices(oldOne *ctrlmeshproto.HTTPFaultInjection, key } } } + for _, oldStringMatch := range oldOne.Match.StringMatch { + if oldStringMatch.MatchType == ctrlmeshproto.StringMatch_NORMAL { + for _, content := range oldStringMatch.Contents { + for _, method := range oldStringMatch.Methods { + urlMethod := indexForRest(content, method) + if s.normalIndexes[urlMethod] != nil { + s.normalIndexes[urlMethod].Delete(key) + if s.normalIndexes[urlMethod].Len() == 0 { + delete(s.normalIndexes, urlMethod) + } + } + } + } + } else if oldStringMatch.MatchType == ctrlmeshproto.StringMatch_REGEXP { + for regKey := range s.regexpIndexes { + if regKey == key { + delete(s.regexpIndexes, key) + } + } + } + } } func indexForResource(namespace, apiGroup, resource, verb string) string { diff --git a/pkg/proxy/http/http_proxy.go b/pkg/proxy/http/http_proxy.go index c1be1ca..3f3eef8 100644 --- a/pkg/proxy/http/http_proxy.go +++ b/pkg/proxy/http/http_proxy.go @@ -37,8 +37,10 @@ import ( ) var ( - enableRestBreaker = os.Getenv(constants.EnvEnableRestCircuitBreaker) == "true" - logger = logf.Log.WithName("http-proxy") + enableRestBreaker = os.Getenv(constants.EnvEnableRestCircuitBreaker) == "true" + enableRestFaultInjection = os.Getenv(constants.EnvEnableRestFaultInjection) == "true" + + logger = logf.Log.WithName("http-proxy") ) type ITProxy interface { @@ -86,16 +88,37 @@ func (t *tproxy) handleHTTP(resp http.ResponseWriter, req *http.Request) { } klog.Infof("handel http request, url: %s ", realEndPointUrl.String()) // faultinjection - result := t.FaultInjector.FaultInjectionRest(req.Header.Get(meshhttp.HeaderMeshRealEndpoint), req.Method) - if result.Abort { - apiErr := utils.HttpToAPIError(int(result.ErrCode), req.Method, result.Message) - resp.Header().Set("Content-Type", "application/json") - resp.WriteHeader(int(apiErr.Code)) - if err := json.NewEncoder(resp).Encode(apiErr); err != nil { - http.Error(resp, fmt.Sprintf("fail to inject fault %v", err), http.StatusInternalServerError) + if enableRestFaultInjection { + klog.Infof("start FaultInjectionRest %s", req.Header.Get(meshhttp.HeaderMeshRealEndpoint)) + result := t.FaultInjector.FaultInjectionRest(req.Header.Get(meshhttp.HeaderMeshRealEndpoint), req.Method) + if result.Abort { + apiErr := utils.HttpToAPIError(int(result.ErrCode), req.Method, result.Message) + if apiErr.Code != http.StatusOK { + resp.Header().Set("Content-Type", "application/json") + resp.WriteHeader(int(apiErr.Code)) + if err := json.NewEncoder(resp).Encode(apiErr); err != nil { + http.Error(resp, fmt.Sprintf("fail to inject fault %v", err), http.StatusInternalServerError) + } + klog.Infof("faultInjection rule, rule: %s", fmt.Sprintf("fault injection, %s, %s,%d", result.Reason, result.Message, result.ErrCode)) + return + } + } + + // normal or regex + klog.Infof("start FaultInjectionNormalOrRegexp %s", realEndPointUrl.Host) + result = t.FaultInjector.FaultInjectionNormalOrRegexp(realEndPointUrl.Host, req.Method) + if result.Abort { + apiErr := utils.HttpToAPIError(int(result.ErrCode), req.Method, result.Message) + if apiErr.Code != http.StatusOK { + resp.Header().Set("Content-Type", "application/json") + resp.WriteHeader(int(apiErr.Code)) + if err := json.NewEncoder(resp).Encode(apiErr); err != nil { + http.Error(resp, fmt.Sprintf("fail to inject fault %v", err), http.StatusInternalServerError) + } + klog.Infof("faultInjection normal or regexp rule, rule: %s", fmt.Sprintf("fault injection, %s, %s,%d", result.Reason, result.Message, result.ErrCode)) + return + } } - klog.Infof("faultInjection rule, rule: %s", fmt.Sprintf("fault injection, %s, %s,%d", result.Reason, result.Message, result.ErrCode)) - return } // circuitbreaker diff --git a/pkg/proxy/http/http_proxy_test.go b/pkg/proxy/http/http_proxy_test.go index 0b5c205..0384a48 100644 --- a/pkg/proxy/http/http_proxy_test.go +++ b/pkg/proxy/http/http_proxy_test.go @@ -31,6 +31,7 @@ import ( ) func TestTProxy(t *testing.T) { + enableRestFaultInjection = true g := gomega.NewGomegaWithT(t) go StartProxy() time.Sleep(time.Second * 2) @@ -40,9 +41,9 @@ func TestTProxy(t *testing.T) { t.Error(err) } - req.Header.Set(meshhttp.HeaderMeshRealEndpoint, "https://www.github.com") + req.Header.Set(meshhttp.HeaderMeshRealEndpoint, "https://github.com") resp, err := http.DefaultClient.Do(req) - g.Expect(resp.StatusCode).Should(gomega.Equal(http.StatusOK)) + g.Expect(resp.StatusCode).Should(gomega.Equal(http.StatusForbidden)) req.Header.Set(meshhttp.HeaderMeshRealEndpoint, "https://www.gayhub.com/foo") resp, err = http.DefaultClient.Do(req) g.Expect(resp.StatusCode).Should(gomega.Equal(http.StatusBadGateway)) @@ -64,7 +65,7 @@ func StartProxy() { HttpMatch: []*ctrlmeshproto.HttpMatch{ { Url: []string{ - "https://www.github.com", + "https://github.com", }, Method: []string{ "POST", @@ -75,7 +76,7 @@ func StartProxy() { }, Abort: &ctrlmeshproto.HTTPFaultInjection_Abort{ Percent: 100, - ErrorType: &ctrlmeshproto.HTTPFaultInjection_Abort_HttpStatus{HttpStatus: http.StatusOK}, + ErrorType: &ctrlmeshproto.HTTPFaultInjection_Abort_HttpStatus{HttpStatus: http.StatusForbidden}, }, }, {