diff --git a/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml b/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml index 858efd0..d0227a8 100644 --- a/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml +++ b/config/crd/bases/ctrlmesh.kusionstack.io_faultinjections.yaml @@ -108,50 +108,40 @@ 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 HTTP requests to RESTful resources as part of HTTP FaultInjection. Each rule can target one or more URLs and HTTP methods. properties: - method: - description: 'Method specifies the http method of - the request, like: PUT, POST, GET, DELETE.' + headers: + description: 'TODO: header match' items: - type: string + properties: + name: + type: string + value: + type: string + required: + - name + type: object type: array - url: - description: URL gives the location of the rest request, - in standard URL form (`scheme://host:port/path`) - items: - type: string - type: array - required: - - method - - url + host: + properties: + exact: + type: string + regex: + type: string + type: object + method: + type: string + path: + properties: + exact: + type: string + regex: + type: string + type: object type: object type: array resources: diff --git a/pkg/apis/ctrlmesh/proto/faultinjection.pb.go b/pkg/apis/ctrlmesh/proto/faultinjection.pb.go index 5aef92d..d0d9340 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection.pb.go +++ b/pkg/apis/ctrlmesh/proto/faultinjection.pb.go @@ -77,52 +77,6 @@ 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 @@ -430,9 +384,8 @@ type Match struct { 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"` - StringMatch []*StringMatch `protobuf:"bytes,3,rep,name=stringMatch,proto3" json:"stringMatch,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"` } func (x *Match) Reset() { @@ -481,25 +434,19 @@ func (x *Match) GetHttpMatch() []*HttpMatch { return nil } -func (x *Match) GetStringMatch() []*StringMatch { - if x != nil { - return x.StringMatch - } - return nil -} - -type StringMatch struct { +type HttpMatch struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - 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"` + Host *MatchContent `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Path *MatchContent `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Headers []*HttpHeader `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty"` } -func (x *StringMatch) Reset() { - *x = StringMatch{} +func (x *HttpMatch) Reset() { + *x = HttpMatch{} if protoimpl.UnsafeEnabled { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -507,13 +454,13 @@ func (x *StringMatch) Reset() { } } -func (x *StringMatch) String() string { +func (x *HttpMatch) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StringMatch) ProtoMessage() {} +func (*HttpMatch) ProtoMessage() {} -func (x *StringMatch) ProtoReflect() protoreflect.Message { +func (x *HttpMatch) 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)) @@ -525,43 +472,50 @@ func (x *StringMatch) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StringMatch.ProtoReflect.Descriptor instead. -func (*StringMatch) Descriptor() ([]byte, []int) { +// Deprecated: Use HttpMatch.ProtoReflect.Descriptor instead. +func (*HttpMatch) Descriptor() ([]byte, []int) { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{5} } -func (x *StringMatch) GetMatchType() StringMatch_StringMatchType { +func (x *HttpMatch) GetHost() *MatchContent { if x != nil { - return x.MatchType + return x.Host } - return StringMatch_NORMAL + return nil } -func (x *StringMatch) GetContents() []string { +func (x *HttpMatch) GetPath() *MatchContent { if x != nil { - return x.Contents + return x.Path } return nil } -func (x *StringMatch) GetMethods() []string { +func (x *HttpMatch) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *HttpMatch) GetHeaders() []*HttpHeader { if x != nil { - return x.Methods + return x.Headers } return nil } -type HttpMatch struct { +type MatchContent 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"` + Exact string `protobuf:"bytes,1,opt,name=exact,proto3" json:"exact,omitempty"` + Regex string `protobuf:"bytes,2,opt,name=regex,proto3" json:"regex,omitempty"` } -func (x *HttpMatch) Reset() { - *x = HttpMatch{} +func (x *MatchContent) Reset() { + *x = MatchContent{} if protoimpl.UnsafeEnabled { mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -569,13 +523,13 @@ func (x *HttpMatch) Reset() { } } -func (x *HttpMatch) String() string { +func (x *MatchContent) String() string { return protoimpl.X.MessageStringOf(x) } -func (*HttpMatch) ProtoMessage() {} +func (*MatchContent) ProtoMessage() {} -func (x *HttpMatch) ProtoReflect() protoreflect.Message { +func (x *MatchContent) 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)) @@ -587,23 +541,78 @@ func (x *HttpMatch) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use HttpMatch.ProtoReflect.Descriptor instead. -func (*HttpMatch) Descriptor() ([]byte, []int) { +// Deprecated: Use MatchContent.ProtoReflect.Descriptor instead. +func (*MatchContent) Descriptor() ([]byte, []int) { return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{6} } -func (x *HttpMatch) GetUrl() []string { +func (x *MatchContent) GetExact() string { if x != nil { - return x.Url + return x.Exact } - return nil + return "" } -func (x *HttpMatch) GetMethod() []string { +func (x *MatchContent) GetRegex() string { if x != nil { - return x.Method + return x.Regex } - return nil + return "" +} + +type HttpHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *HttpHeader) Reset() { + *x = HttpHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HttpHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpHeader) ProtoMessage() {} + +func (x *HttpHeader) ProtoReflect() protoreflect.Message { + 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 { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpHeader.ProtoReflect.Descriptor instead. +func (*HttpHeader) Descriptor() ([]byte, []int) { + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{7} +} + +func (x *HttpHeader) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HttpHeader) GetValue() string { + if x != nil { + return x.Value + } + return "" } // Describes how to match K8s resources. @@ -643,7 +652,7 @@ type ResourceMatch struct { func (x *ResourceMatch) Reset() { *x = ResourceMatch{} 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) } @@ -656,7 +665,7 @@ func (x *ResourceMatch) String() string { func (*ResourceMatch) ProtoMessage() {} func (x *ResourceMatch) 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 { @@ -669,7 +678,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{7} + return file_pkg_apis_ctrlmesh_proto_faultinjection_proto_rawDescGZIP(), []int{8} } func (x *ResourceMatch) GetApiGroups() []string { @@ -718,7 +727,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[8] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -731,7 +740,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[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 { @@ -797,7 +806,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[9] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -810,7 +819,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[9] + mi := &file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -973,49 +982,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, 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, + 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, 0xa2, 0x01, + 0x0a, 0x09, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x27, 0x0a, 0x04, 0x68, + 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x04, + 0x68, 0x6f, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, + 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, + 0x74, 0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x73, 0x22, 0x3a, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x22, 0x36, + 0x0a, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 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 ( @@ -1030,42 +1039,43 @@ 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, 2) -var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +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, 11) var file_pkg_apis_ctrlmesh_proto_faultinjection_proto_goTypes = []interface{}{ (FaultInjection_Option)(0), // 0: proto.FaultInjection.Option - (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 - (*StringMatch)(nil), // 7: proto.StringMatch - (*HttpMatch)(nil), // 8: proto.HttpMatch + (*FaultInjectConfigResp)(nil), // 1: proto.FaultInjectConfigResp + (*FaultInjection)(nil), // 2: proto.FaultInjection + (*HTTPFaultInjection)(nil), // 3: proto.HTTPFaultInjection + (*EffectiveTimeRange)(nil), // 4: proto.EffectiveTimeRange + (*Match)(nil), // 5: proto.Match + (*HttpMatch)(nil), // 6: proto.HttpMatch + (*MatchContent)(nil), // 7: proto.MatchContent + (*HttpHeader)(nil), // 8: proto.HttpHeader (*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{ - 4, // 0: proto.FaultInjection.httpFaultInjections:type_name -> proto.HTTPFaultInjection + 3, // 0: proto.FaultInjection.httpFaultInjections:type_name -> proto.HTTPFaultInjection 0, // 1: proto.FaultInjection.option:type_name -> proto.FaultInjection.Option 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 - 5, // 5: proto.HTTPFaultInjection.effective_time:type_name -> proto.EffectiveTimeRange + 5, // 4: proto.HTTPFaultInjection.match:type_name -> proto.Match + 4, // 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 + 6, // 7: proto.Match.httpMatch:type_name -> proto.HttpMatch + 7, // 8: proto.HttpMatch.host:type_name -> proto.MatchContent + 7, // 9: proto.HttpMatch.path:type_name -> proto.MatchContent + 8, // 10: proto.HttpMatch.headers:type_name -> proto.HttpHeader + 12, // 11: proto.HTTPFaultInjection.Delay.fixed_delay:type_name -> google.protobuf.Duration + 2, // 12: proto.FaultInject.SendConfig:input_type -> proto.FaultInjection + 1, // 13: proto.FaultInject.SendConfig:output_type -> proto.FaultInjectConfigResp + 13, // [13:14] is the sub-list for method output_type + 12, // [12:13] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() } @@ -1135,7 +1145,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.(*StringMatch); i { + switch v := v.(*HttpMatch); i { case 0: return &v.state case 1: @@ -1147,7 +1157,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.(*HttpMatch); i { + switch v := v.(*MatchContent); i { case 0: return &v.state case 1: @@ -1159,7 +1169,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.(*ResourceMatch); i { + switch v := v.(*HttpHeader); i { case 0: return &v.state case 1: @@ -1171,7 +1181,7 @@ 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 { + switch v := v.(*ResourceMatch); i { case 0: return &v.state case 1: @@ -1183,6 +1193,18 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9].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[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HTTPFaultInjection_Abort); i { case 0: return &v.state @@ -1195,10 +1217,10 @@ func file_pkg_apis_ctrlmesh_proto_faultinjection_proto_init() { } } } - file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[8].OneofWrappers = []interface{}{ + file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9].OneofWrappers = []interface{}{ (*HTTPFaultInjection_Delay_FixedDelay)(nil), } - file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[9].OneofWrappers = []interface{}{ + file_pkg_apis_ctrlmesh_proto_faultinjection_proto_msgTypes[10].OneofWrappers = []interface{}{ (*HTTPFaultInjection_Abort_HttpStatus)(nil), (*HTTPFaultInjection_Abort_GrpcStatus)(nil), (*HTTPFaultInjection_Abort_Http2Error)(nil), @@ -1208,8 +1230,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: 2, - NumMessages: 10, + NumEnums: 1, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/apis/ctrlmesh/proto/faultinjection.proto b/pkg/apis/ctrlmesh/proto/faultinjection.proto index 8d79e6b..91db734 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection.proto +++ b/pkg/apis/ctrlmesh/proto/faultinjection.proto @@ -111,22 +111,23 @@ message EffectiveTimeRange { 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 { + MatchContent host = 1; + MatchContent path = 2; + string method = 3; + repeated HttpHeader headers = 4; } -message HttpMatch { - repeated string url = 1; - repeated string method = 2; +message MatchContent { + string exact = 1; + string regex = 2; +} + +message HttpHeader { + string name = 1; + string value = 2; } // Describes how to match K8s resources. diff --git a/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go b/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go index a28efc2..af126d0 100644 --- a/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go +++ b/pkg/apis/ctrlmesh/proto/faultinjection_deepcopy.gen.go @@ -152,45 +152,66 @@ func (in *Match) DeepCopyInterface() interface{} { return in.DeepCopy() } -// DeepCopyInto supports using StringMatch within kubernetes types, where deepcopy-gen is used. -func (in *StringMatch) DeepCopyInto(out *StringMatch) { - p := proto.Clone(in).(*StringMatch) +// 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 StringMatch. Required by controller-gen. -func (in *StringMatch) DeepCopy() *StringMatch { +// 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(StringMatch) + out := new(HttpMatch) in.DeepCopyInto(out) return out } -// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. Required by controller-gen. -func (in *StringMatch) DeepCopyInterface() interface{} { +// 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() } -// 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 MatchContent within kubernetes types, where deepcopy-gen is used. +func (in *MatchContent) DeepCopyInto(out *MatchContent) { + p := proto.Clone(in).(*MatchContent) *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 MatchContent. Required by controller-gen. +func (in *MatchContent) DeepCopy() *MatchContent { if in == nil { return nil } - out := new(HttpMatch) + out := new(MatchContent) 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 MatchContent. Required by controller-gen. +func (in *MatchContent) DeepCopyInterface() interface{} { + return in.DeepCopy() +} + +// DeepCopyInto supports using HttpHeader within kubernetes types, where deepcopy-gen is used. +func (in *HttpHeader) DeepCopyInto(out *HttpHeader) { + p := proto.Clone(in).(*HttpHeader) + *out = *p +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpHeader. Required by controller-gen. +func (in *HttpHeader) DeepCopy() *HttpHeader { + if in == nil { + return nil + } + out := new(HttpHeader) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new HttpHeader. Required by controller-gen. +func (in *HttpHeader) DeepCopyInterface() interface{} { return in.DeepCopy() } diff --git a/pkg/apis/ctrlmesh/utils/conv/faultconv.go b/pkg/apis/ctrlmesh/utils/conv/faultconv.go index bd3f5e1..b18ad72 100644 --- a/pkg/apis/ctrlmesh/utils/conv/faultconv.go +++ b/pkg/apis/ctrlmesh/utils/conv/faultconv.go @@ -76,7 +76,7 @@ func ConvertHTTPFaultInjection(faultInjection *ctrlmeshv1alpha1.HTTPFaultInjecti } } if faultInjection.Match != nil { - protoFaultInjection.Match = ConvertHTTPMatch(faultInjection.Match) + protoFaultInjection.Match = ConvertMatch(faultInjection.Match) } if faultInjection.EffectiveTime != nil { @@ -85,35 +85,42 @@ func ConvertHTTPFaultInjection(faultInjection *ctrlmeshv1alpha1.HTTPFaultInjecti return protoFaultInjection } -func ConvertHTTPMatch(match *ctrlmeshv1alpha1.Match) *ctrlmeshproto.Match { - Match := &ctrlmeshproto.Match{} +func ConvertMatch(match *ctrlmeshv1alpha1.Match) *ctrlmeshproto.Match { + protoMatch := &ctrlmeshproto.Match{} if match.HttpMatch != nil { - Match.HttpMatch = make([]*ctrlmeshproto.HttpMatch, len(match.HttpMatch)) - for i, restRule := range match.HttpMatch { - Match.HttpMatch[i] = &ctrlmeshproto.HttpMatch{} - if restRule.URL != nil { - Match.HttpMatch[i].Url = make([]string, len(restRule.URL)) - copy(Match.HttpMatch[i].Url, restRule.URL) + protoMatch.HttpMatch = make([]*ctrlmeshproto.HttpMatch, len(match.HttpMatch)) + for i, httpMatch := range match.HttpMatch { + temp := &ctrlmeshproto.HttpMatch{ + Host: ConvertMatchContent(httpMatch.Host), + Path: ConvertMatchContent(httpMatch.Path), + Method: httpMatch.Method, } - if restRule.Method != nil { - Match.HttpMatch[i].Method = make([]string, len(restRule.Method)) - copy(Match.HttpMatch[i].Method, restRule.Method) + for _, header := range httpMatch.Headers { + temp.Headers = append(temp.Headers, &ctrlmeshproto.HttpHeader{ + Name: header.Name, + Value: header.Name, + }) } + protoMatch.HttpMatch[i] = temp } } if match.Resources != nil { - Match.Resources = make([]*ctrlmeshproto.ResourceMatch, len(match.Resources)) + protoMatch.Resources = make([]*ctrlmeshproto.ResourceMatch, len(match.Resources)) for i, relatedResource := range match.Resources { - Match.Resources[i] = ConvertRelatedResources(relatedResource) + protoMatch.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 protoMatch +} + +func ConvertMatchContent(content *ctrlmeshv1alpha1.MatchContent) *ctrlmeshproto.MatchContent { + if content == nil { + return nil + } + return &ctrlmeshproto.MatchContent{ + Exact: content.Exact, + Regex: content.Regex, } - return Match } func ConvertEffectiveTime(timeRange *ctrlmeshv1alpha1.EffectiveTimeRange) *ctrlmeshproto.EffectiveTimeRange { @@ -164,25 +171,3 @@ 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 bac0702..a749c13 100644 --- a/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go +++ b/pkg/apis/ctrlmesh/v1alpha1/faultinjection_types.go @@ -22,11 +22,6 @@ import ( 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"` @@ -54,18 +49,21 @@ type ResourceMatch struct { // HttpMatch specifies the criteria for matching HTTP requests to RESTful resources // as part of HTTP FaultInjection. Each rule can target one or more URLs and HTTP methods. type HttpMatch struct { - // URL gives the location of the rest request, in standard URL form (`scheme://host:port/path`) - URL []string `json:"url"` - // Method specifies the http method of the request, like: PUT, POST, GET, DELETE. - Method []string `json:"method"` + Host *MatchContent `json:"host,omitempty"` + Path *MatchContent `json:"path,omitempty"` + Method string `json:"method,omitempty"` + //TODO: header match + Headers []*HttpHeader `json:"headers,omitempty"` +} + +type MatchContent struct { + Exact string `json:"exact,omitempty"` + Regex string `json:"regex,omitempty"` } -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"` +type HttpHeader struct { + Name string `json:"name"` + Value string `json:"value,omitempty"` } // Match defines a set of rules and criteria for matching incoming HTTP requests. @@ -74,8 +72,6 @@ type StringMatch struct { 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 2acc2e8..fcf1f1e 100644 --- a/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/ctrlmesh/v1alpha1/zz_generated.deepcopy.go @@ -447,18 +447,44 @@ func (in *HTTPFaultInjectionDelay) DeepCopy() *HTTPFaultInjectionDelay { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HttpHeader) DeepCopyInto(out *HttpHeader) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpHeader. +func (in *HttpHeader) DeepCopy() *HttpHeader { + if in == nil { + return nil + } + out := new(HttpHeader) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HttpMatch) DeepCopyInto(out *HttpMatch) { *out = *in - if in.URL != nil { - in, out := &in.URL, &out.URL - *out = make([]string, len(*in)) - copy(*out, *in) + if in.Host != nil { + in, out := &in.Host, &out.Host + *out = new(MatchContent) + **out = **in } - if in.Method != nil { - in, out := &in.Method, &out.Method - *out = make([]string, len(*in)) - copy(*out, *in) + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(MatchContent) + **out = **in + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make([]*HttpHeader, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(HttpHeader) + **out = **in + } + } } } @@ -728,17 +754,6 @@ 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. @@ -751,6 +766,21 @@ func (in *Match) DeepCopy() *Match { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MatchContent) DeepCopyInto(out *MatchContent) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchContent. +func (in *MatchContent) DeepCopy() *MatchContent { + if in == nil { + return nil + } + out := new(MatchContent) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectLimiter) DeepCopyInto(out *ObjectLimiter) { *out = *in @@ -1132,31 +1162,6 @@ func (in *ShardingConfigWebhookConfiguration) DeepCopy() *ShardingConfigWebhookC return out } -// 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. -func (in *StringMatch) DeepCopy() *StringMatch { - if in == nil { - return nil - } - out := new(StringMatch) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetStatus) DeepCopyInto(out *TargetStatus) { *out = *in diff --git a/pkg/proxy/faultinjection/injector.go b/pkg/proxy/faultinjection/injector.go new file mode 100644 index 0000000..09e0676 --- /dev/null +++ b/pkg/proxy/faultinjection/injector.go @@ -0,0 +1,67 @@ +/* +Copyright 2023 The KusionStack 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 faultinjection + +import ( + "encoding/json" + "net/http" + "time" + + "k8s.io/klog/v2" + + "github.com/KusionStack/controller-mesh/pkg/utils" +) + +type Injector interface { + Do(w http.ResponseWriter, req *http.Request) (abort bool) +} + +type abortWithDelayInjector struct { + Abort bool + delay time.Duration + code int + message string +} + +func (m *abortWithDelayInjector) Do(w http.ResponseWriter, req *http.Request) bool { + if m.delay != 0 { + <-time.After(m.delay) + } + if m.code != http.StatusOK && m.code != 0 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(m.code) + apiErr := utils.HttpToAPIError(m.code, req.Method, m.message) + if err := json.NewEncoder(w).Encode(apiErr); err != nil { + klog.Errorf("failed to write api error response: %v", err) + return true + } + klog.Infof("abort by faultInjection, %s, %s, %d, with delay %ss", apiErr.Reason, apiErr.Message, apiErr.Code, m.delay/time.Second) + return true + } + klog.Infof("delay by faultInjection, %ss", m.delay/time.Second) + return false +} + +func (m *abortWithDelayInjector) AddDelay(d time.Duration) { + m.delay += d +} + +func (m *abortWithDelayInjector) AddAbort(code int, message string) { + m.Abort = true + m.code = code + m.message = message +} diff --git a/pkg/proxy/faultinjection/lease.go b/pkg/proxy/faultinjection/lease.go deleted file mode 100644 index 2bf5ae2..0000000 --- a/pkg/proxy/faultinjection/lease.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2023 The KusionStack 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 faultinjection - -import ( - "context" - "sync" - "time" - - "k8s.io/client-go/util/workqueue" -) - -type lease struct { - mu sync.RWMutex - stateQueue workqueue.DelayingInterface - stateSet map[string]*state - - ctx context.Context -} - -func newFaultInjectionLease(ctx context.Context) *lease { - result := &lease{ - stateQueue: workqueue.NewDelayingQueue(), - stateSet: map[string]*state{}, - ctx: ctx, - } - go result.processingLoop() - return result -} - -func (l *lease) processingLoop() { - go func() { - <-l.ctx.Done() - l.stateQueue.ShutDown() - }() - for { - obj, shutdown := l.stateQueue.Get() - if shutdown { - return - } - st := obj.(*state) - recoverAt := st.read() - if recoverAt != nil && time.Now().Before(recoverAt.Time) { - // recover time changed, requeue - l.stateQueue.AddAfter(st, time.Until(recoverAt.Time)) - } else { - l.mu.Lock() - delete(l.stateSet, st.key) - l.mu.Unlock() - } - l.stateQueue.Done(st) - } -} diff --git a/pkg/proxy/faultinjection/manager.go b/pkg/proxy/faultinjection/manager.go index a984ff8..f904fb0 100644 --- a/pkg/proxy/faultinjection/manager.go +++ b/pkg/proxy/faultinjection/manager.go @@ -18,14 +18,12 @@ package faultinjection import ( "context" - "encoding/json" "errors" "fmt" "math/rand" "net/http" "net/url" - "strings" "sync" "time" @@ -34,7 +32,6 @@ import ( "k8s.io/klog/v2" ctrlmeshproto "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/proto" - "github.com/KusionStack/controller-mesh/pkg/utils" ) const timeLayout = "15:04:05" @@ -44,7 +41,7 @@ var ( ) type ManagerInterface interface { - FaultInjector + FaultInjectorManager Sync(config *ctrlmeshproto.FaultInjection) (*ctrlmeshproto.FaultInjectConfigResp, error) } @@ -55,10 +52,9 @@ type FaultInjectionResult struct { ErrCode int32 } -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) +type FaultInjectorManager interface { + GetInjectorByUrl(url *url.URL, method string) Injector + GetInjectorByResource(namespace, apiGroup, resource, verb string) Injector HandlerWrapper() func(http.Handler) http.Handler } @@ -150,123 +146,99 @@ func (m *manager) HandlerWrapper() func(http.Handler) http.Handler { } } -func (m *manager) FaultInjectionNormalOrRegexp(URL string, method string) (result *FaultInjectionResult) { - // regexp and normal - now := time.Now() - 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 - } - } +func (m *manager) GetInjectorByUrl(url *url.URL, method string) Injector { + faultInjections := m.matchHTTPFaultInjection(url, method) + return m.getInjector(faultInjections) +} - 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 - } - } +func (m *manager) matchHTTPFaultInjection(url *url.URL, method string) (res []*ctrlmeshproto.HTTPFaultInjection) { + for _, rule := range m.faultInjectionStore.restMatchRules { + if m.matchRest(rule.Match, url, method) { + res = append(res, rule) } } - result = &FaultInjectionResult{Abort: false, Reason: "No rule match"} - return result + return } -func (m *manager) FaultInjectionRest(URL string, method string) (result *FaultInjectionResult) { - now := time.Now() - urls := generateWildcardUrls(URL, method) - for _, url := range urls { - faultInjections, _ := m.faultInjectionStore.byIndex(IndexRest, url) - if len(faultInjections) == 0 { +func (m *manager) matchRest(match *ctrlmeshproto.Match, url *url.URL, method string) bool { + for _, httpMatch := range match.HttpMatch { + if httpMatch.Method != "*" && httpMatch.Method != "" && httpMatch.Method != method { continue } - 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 + if httpMatch.Host != nil && !m.matchContent(httpMatch.Host, url.Host) { + continue + } + if httpMatch.Path != nil && !m.matchContent(httpMatch.Path, url.Path) { + continue + } + return true } - result = &FaultInjectionResult{Abort: false, Reason: "No rule match"} - return result + return false } -func generateWildcardUrls(URL string, method string) []string { - var result []string - result = append(result, indexForRest(URL, method)) - URL = strings.TrimSuffix(URL, "/") - u, err := url.Parse(URL) - if err != nil { - klog.Errorf("failed to url, URL: %s, method: %s,err: %v", URL, method, err) - return result +func (m *manager) matchContent(mc *ctrlmeshproto.MatchContent, content string) bool { + if mc.Exact != "" { + return mc.Exact == content } - if len(u.Path) > 0 { - subPaths := strings.Split(u.Path, "/") - for i := len(subPaths) - 1; i > 0; i-- { - subPath := u.Scheme + "://" + u.Host + strings.Join(subPaths[:i], "/") + "/*" - result = append(result, indexForRest(subPath, method)) + if mc.Regex != "" { + regex, ok := m.faultInjectionStore.regexMap[mc.Regex] + if ok { + return regex.MatchString(content) } } - - return result + return false } -func (m *manager) FaultInjectionResource(namespace, apiGroup, resource, verb string) (result *FaultInjectionResult) { - now := time.Now() - seeds := generateWildcardSeeds(namespace, apiGroup, resource, verb) - for _, seed := range seeds { - faultInjections, _ := m.faultInjectionStore.byIndex(IndexResource, seed) - if len(faultInjections) == 0 { - continue +func (m *manager) GetInjectorByResource(namespace, apiGroup, resource, verb string) Injector { + var fjs []*ctrlmeshproto.HTTPFaultInjection + for _, rule := range m.faultInjectionStore.resourcesMatchRules { + if m.resourceMatch(rule.Match.Resources, namespace, apiGroup, resource, verb) { + fjs = append(fjs, rule) } - 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"} - return result + return m.getInjector(fjs) } -func generateWildcardSeeds(namespace, apiGroup, resource, verb string) []string { - /* priority strategy - 1、if wildcard number not equal, less wildcard number priority higher. eg. 0* > 1* > 2* > 3* > 4* - 2、if wildcard number equal, the priority order is verb > resource > apiGroup > namespace. - eg. verb exist first match verb, then match resource, after match apiGroup, last match namespace - */ - result := []string{ - // zero wildcard - indexForResource(namespace, apiGroup, resource, verb), - // one wildcard - indexForResource("*", apiGroup, resource, verb), - indexForResource(namespace, "*", resource, verb), - indexForResource(namespace, apiGroup, "*", verb), - indexForResource(namespace, apiGroup, resource, "*"), - // two wildcard - indexForResource("*", "*", resource, verb), - indexForResource("*", apiGroup, "*", verb), - indexForResource(namespace, "*", "*", verb), - indexForResource("*", apiGroup, resource, "*"), - indexForResource(namespace, "*", resource, "*"), - indexForResource(namespace, apiGroup, "*", "*"), - // three wildcard - indexForResource("*", "*", "*", verb), - indexForResource("*", "*", resource, "*"), - indexForResource("*", apiGroup, "*", "*"), - indexForResource(namespace, "*", "*", "*"), - // four wildcard - indexForResource("*", "*", "*", "*"), +func (m *manager) resourceMatch(matchs []*ctrlmeshproto.ResourceMatch, namespace, apiGroup, resource, verb string) bool { + for _, match := range matchs { + for _, val := range match.ApiGroups { + if val == "*" { + break + } + if val != apiGroup { + continue + } + } + for _, val := range match.Resources { + if val == "*" { + break + } + if val != resource { + continue + } + } + for _, val := range match.Verbs { + if val == "*" { + break + } + if val != verb { + continue + } + } + for _, val := range match.Namespaces { + if val == "*" { + break + } + if val != namespace { + continue + } + } + return true } - return result + return false } -func withFaultInjection(injector FaultInjector, handler http.Handler) http.Handler { +func withFaultInjection(injectorManager FaultInjectorManager, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() requestInfo, ok := apirequest.RequestInfoFrom(ctx) @@ -275,70 +247,39 @@ func withFaultInjection(injector FaultInjector, handler http.Handler) http.Handl responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context")) return } - result := injector.FaultInjectionResource(requestInfo.Namespace, requestInfo.APIGroup, requestInfo.Resource, requestInfo.Verb) - - if result.Abort { - apiErr := utils.HttpToAPIError(int(result.ErrCode), req.Method, result.Message) - if apiErr.Code != http.StatusOK { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(int(apiErr.Code)) - 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 - } + injector := injectorManager.GetInjectorByResource(requestInfo.Namespace, requestInfo.APIGroup, requestInfo.Resource, requestInfo.Verb) + if !injector.Do(w, req) { + handler.ServeHTTP(w, req) } - handler.ServeHTTP(w, req) }) } -func (m *manager) doFaultInjection(faultInjections []*ctrlmeshproto.HTTPFaultInjection) *FaultInjectionResult { - result := &FaultInjectionResult{ - Abort: false, - Reason: "Default allow", - } +func (m *manager) getInjector(faultInjections []*ctrlmeshproto.HTTPFaultInjection) Injector { + injector := &abortWithDelayInjector{} for idx := range faultInjections { - if faultInjections[idx].EffectiveTime != nil && !isEffectiveTimeRange(faultInjections[idx].EffectiveTime) { - fmt.Println("effective time is not in range", faultInjections[idx].EffectiveTime) + //klog.Infof("FaultInjection %s effective time is not in range, %v", faultInjections[idx].Name, faultInjections[idx].EffectiveTime) continue } - - if faultInjections[idx].Delay != nil { - if isInpercentRange(faultInjections[idx].Delay.Percent) { - delay := faultInjections[idx].Delay.GetFixedDelay() - delayDuration := delay.AsDuration() - klog.Infof("Delaying time: %v ", delayDuration) - time.Sleep(delayDuration) - } + if faultInjections[idx].Delay != nil && isInPercentRange(faultInjections[idx].Delay.Percent) { + injector.AddDelay(faultInjections[idx].Delay.GetFixedDelay().AsDuration()) } - if faultInjections[idx].Abort != nil { - if isInpercentRange(faultInjections[idx].Abort.Percent) { - result.Abort = true - 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 - } - + if !injector.Abort && faultInjections[idx].Abort != nil && isInPercentRange(faultInjections[idx].Abort.Percent) { + injector.AddAbort(int(faultInjections[idx].Abort.GetHttpStatus()), + fmt.Sprintf("the fault injection is triggered. Limiting rule name: %s", faultInjections[idx].Name)) } } - return result + return injector } -func isInpercentRange(value float64) bool { +func isInPercentRange(value float64) bool { if value < 0 || value > 100 { - fmt.Println("Value must be between 0 and 100") return false } if value == 0 { return true } randomNumber := randNum.Float64() * 100 - return randomNumber < value } diff --git a/pkg/proxy/faultinjection/manager_test.go b/pkg/proxy/faultinjection/manager_test.go index 9090223..a5caeaf 100644 --- a/pkg/proxy/faultinjection/manager_test.go +++ b/pkg/proxy/faultinjection/manager_test.go @@ -19,6 +19,7 @@ package faultinjection import ( "context" "fmt" + "net/url" "os" "testing" @@ -26,10 +27,11 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + 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() { @@ -66,11 +68,33 @@ func TestStringMatch(t *testing.T) { Resources: []string{"pod"}, }, }, - ContentMatch: []*ctrlmeshv1alpha1.StringMatch{ + HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ + { + Host: &ctrlmeshv1alpha1.MatchContent{ + Exact: "test1.com", + }, + Method: "POST", + }, + { + Host: &ctrlmeshv1alpha1.MatchContent{ + Exact: "test2.com", + }, + Path: &ctrlmeshv1alpha1.MatchContent{ + Exact: "/a/b", + }, + }, + { + Host: &ctrlmeshv1alpha1.MatchContent{ + Regex: "(test3[a-z]+)", + }, + }, { - MatchType: ctrlmeshv1alpha1.StringMatchTypeNormal, - Contents: []string{"www.hello.com", "www.testa.com"}, - Methods: []string{"POST", "GET"}, + Host: &ctrlmeshv1alpha1.MatchContent{ + Exact: "test4.com", + }, + Path: &ctrlmeshv1alpha1.MatchContent{ + Regex: "/abc/", + }, }, }, }, @@ -85,217 +109,60 @@ func TestStringMatch(t *testing.T) { _, err := mgr.Sync(protoFault) g.Expect(err).Should(gomega.BeNil()) - // test limit delete pod + testUrl, _ := url.Parse("https://test1.com") + injector := mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeTrue()) - 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()) + testUrl, _ = url.Parse("https://test1.com") + injector = mgr.GetInjectorByUrl(testUrl, "GET") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(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"}, - }, - }, - }, - }, - }, - }, - } + testUrl, _ = url.Parse("https://test1.com/a") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeTrue()) - protoFault2 := conv.ConvertFaultInjection(fi2) - protoFault2.Option = ctrlmeshproto.FaultInjection_UPDATE - _, err = mgr.Sync(protoFault2) - g.Expect(err).Should(gomega.BeNil()) + testUrl, _ = url.Parse("https://test2.com/a/b") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(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.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()) + testUrl, _ = url.Parse("https://test2.com") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(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"}, - }, - }, - }, - }, - }, - }, - } + testUrl, _ = url.Parse("https://test2.com/a/c") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) - 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()) + testUrl, _ = url.Parse("https://test3x.com/a/c") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(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()) + testUrl, _ = url.Parse("https://test3.com") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) - 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")) + testUrl, _ = url.Parse("https://test4.com/abc/d") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeTrue()) - 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()) + protoFault.Option = ctrlmeshproto.FaultInjection_DELETE + _, err = mgr.Sync(protoFault) + + testUrl, _ = url.Parse("https://test1.com/a") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) + + testUrl, _ = url.Parse("https://test2.com/a/b") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) + + testUrl, _ = url.Parse("https://test3x.com/a/c") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) - 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()) + testUrl, _ = url.Parse("https://test4.com/abc/d") + injector = mgr.GetInjectorByUrl(testUrl, "POST") + g.Expect(injector.(*abortWithDelayInjector).Abort).Should(gomega.BeFalse()) } func TestIsEffectiveTimeRange(t *testing.T) { diff --git a/pkg/proxy/faultinjection/store.go b/pkg/proxy/faultinjection/store.go index 64bd60f..ee33843 100644 --- a/pkg/proxy/faultinjection/store.go +++ b/pkg/proxy/faultinjection/store.go @@ -22,78 +22,28 @@ import ( "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" ) -const ( - IndexResource = "resource" - IndexRest = "rest" -) - -var ( - indexFunctions = map[string]func(faultinjection *ctrlmeshproto.HTTPFaultInjection) []string{ - IndexResource: indexFuncForResource, - IndexRest: indexFuncForRest, - } -) - -type index map[string]sets.Set[string] - -type indices map[string]index - -type state struct { - mu sync.RWMutex - key string - lastTransitionTime *metav1.Time -} - -func (s *state) read() (lastTime *metav1.Time) { - s.mu.RLock() - defer s.mu.RUnlock() - if s.lastTransitionTime != nil { - lastTime = s.lastTransitionTime.DeepCopy() - } - 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 {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 + resourcesMatchRules map[string]*ctrlmeshproto.HTTPFaultInjection + restMatchRules map[string]*ctrlmeshproto.HTTPFaultInjection + regexMap map[string]*regexp.Regexp ctx context.Context } func newFaultInjectionStore(ctx context.Context) *store { s := &store{ - rules: make(map[string]*ctrlmeshproto.HTTPFaultInjection), - indices: indices{}, - faultInjectionLease: newFaultInjectionLease(ctx), + regexMap: map[string]*regexp.Regexp{}, + resourcesMatchRules: map[string]*ctrlmeshproto.HTTPFaultInjection{}, + restMatchRules: map[string]*ctrlmeshproto.HTTPFaultInjection{}, ctx: ctx, - normalIndexes: make(map[string]sets.Set[string]), - regexpIndexes: make(map[string][]*regexpInfo), } return s } @@ -102,152 +52,44 @@ func newFaultInjectionStore(ctx context.Context) *store { func (s *store) createOrUpdateRule(key string, faultInjection *ctrlmeshproto.HTTPFaultInjection) { s.mu.Lock() defer s.mu.Unlock() - - oldOne, ok := s.rules[key] - if !ok { - // all new, just assign rules and states, and update indices - s.rules[key] = faultInjection - s.updateIndices(nil, faultInjection, key) - } else { - // there is an old one, assign the new rule, update indices - s.rules[key] = faultInjection.DeepCopy() - s.updateIndices(oldOne, faultInjection, key) - } -} - -// deleteRule deletes rules by ley -func (s *store) deleteRule(key string) { - s.mu.Lock() - defer s.mu.Unlock() - - if obj, ok := s.rules[key]; ok { - s.deleteFromIndices(obj, key) - delete(s.rules, key) - } -} - -// byIndex lists rules by a specific index -func (s *store) byIndex(indexName, indexedValue string) ([]*ctrlmeshproto.HTTPFaultInjection, []*state) { - s.mu.RLock() - defer s.mu.RUnlock() - - indexFunc := indexFunctions[indexName] - if indexFunc == nil { - return nil, nil - } - idx := s.indices[indexName] - set := idx[indexedValue] - - limitings := make([]*ctrlmeshproto.HTTPFaultInjection, 0, set.Len()) - states := make([]*state, 0, set.Len()) - for key := range set { - limitings = append(limitings, s.rules[key]) - - } - return limitings, states -} - -// updateIndices updates current indices of the rules -func (s *store) updateIndices(oldOne, newOne *ctrlmeshproto.HTTPFaultInjection, key string) { - if oldOne != nil { - s.deleteFromIndices(oldOne, key) - } - for name, indexFunc := range indexFunctions { - indexValues := indexFunc(newOne) - idx := s.indices[name] - if idx == nil { - idx = index{} - s.indices[name] = idx - } - - for _, indexValue := range indexValues { - set := idx[indexValue] - if set == nil { - set = sets.New[string]() - idx[indexValue] = set - } - set.Insert(key) - } + if len(faultInjection.Match.Resources) > 0 { + s.resourcesMatchRules[key] = faultInjection } - - 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) + if len(faultInjection.Match.HttpMatch) > 0 { + s.restMatchRules[key] = faultInjection + for _, match := range faultInjection.Match.HttpMatch { + if match.Path != nil && match.Path.Regex != "" { + reg, err := regexp.Compile(match.Path.Regex) + if err == nil { + s.regexMap[match.Path.Regex] = reg + } else { + klog.Errorf("fail to compile regexp %s", match.Path.Regex) } } - } 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) + if match.Host != nil && match.Host.Regex != "" { + reg, err := regexp.Compile(match.Host.Regex) + if err == nil { + s.regexMap[match.Host.Regex] = reg } else { - for _, method := range newStringMatch.Methods { - s.regexpIndexes[key] = append(s.regexpIndexes[key], ®expInfo{reg: reg, method: method}) - } + klog.Errorf("fail to compile regexp %s", match.Host.Regex) } } } - } - } -// deleteFromIndices deletes indices of specified keys -func (s *store) deleteFromIndices(oldOne *ctrlmeshproto.HTTPFaultInjection, key string) { - for name, indexFunc := range indexFunctions { - indexValues := indexFunc(oldOne) - idx := s.indices[name] - if idx == nil { - continue - } - for _, indexValue := range indexValues { - set := idx[indexValue] - if set != nil { - set.Delete(key) - if len(set) == 0 { - delete(idx, indexValue) - } - } - } - } - 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) - } - } - } - } +// deleteRule deletes rules by ley +func (s *store) deleteRule(key string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.resourcesMatchRules, key) + delete(s.restMatchRules, key) } func indexForResource(namespace, apiGroup, resource, verb string) string { return fmt.Sprintf("%s:%s:%s:%s", namespace, apiGroup, resource, verb) } -func indexForRest(URL, method string) string { - return fmt.Sprintf("%s:%s", URL, method) -} - func indexFuncForResource(faultinjection *ctrlmeshproto.HTTPFaultInjection) []string { var result []string for _, rule := range faultinjection.Match.Resources { @@ -263,15 +105,3 @@ func indexFuncForResource(faultinjection *ctrlmeshproto.HTTPFaultInjection) []st } return result } - -func indexFuncForRest(faultInjection *ctrlmeshproto.HTTPFaultInjection) []string { - var result []string - for _, rest := range faultInjection.Match.HttpMatch { - for _, url := range rest.Url { - for _, method := range rest.Method { - result = append(result, indexForRest(url, method)) - } - } - } - return result -} diff --git a/pkg/proxy/faultinjection/store_test.go b/pkg/proxy/faultinjection/store_test.go deleted file mode 100644 index 8060bb2..0000000 --- a/pkg/proxy/faultinjection/store_test.go +++ /dev/null @@ -1,371 +0,0 @@ -/* -Copyright 2023 The KusionStack 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 faultinjection - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "github.com/onsi/gomega" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "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" -) - -func init() { - logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) -} - -func TestFaultInjectionStore(t *testing.T) { - fmt.Println("Test_Fault_Injection_Store") - limitingA := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "deletePod", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "50", - }, - Match: &ctrlmeshv1alpha1.Match{ - Resources: []*ctrlmeshv1alpha1.ResourceMatch{ - { - Namespaces: []string{"*"}, - ApiGroups: []string{""}, - Resources: []string{"pod"}, - Verbs: []string{"delete"}, - }, - }, - }, - EffectiveTime: &ctrlmeshv1alpha1.EffectiveTimeRange{ - StartTime: "&v1.Time{Time: time.Date(2023, 4, 14, 8, 0, 0, 0, time.UTC)}", - EndTime: "&v1.Time{Time: time.Date(2023, 4, 28, 18, 0, 0, 0, time.UTC)}", - DaysOfWeek: []int{ - int(time.Monday), // 1 - int(time.Tuesday), // 2 - int(time.Wednesday), // 3 - int(time.Friday), // 5 - }, - DaysOfMonth: []int{}, // 1st and 15th of the month - Months: []int{1, 4, 7, 10, 12}, // January, April, July, October - }, - } - - limitingB := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "createDomain", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Match: &ctrlmeshv1alpha1.Match{ - HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ - { - URL: []string{"https://localhost:80/createDomain"}, - Method: []string{"POST"}, - }, - }, - }, - } - store := newFaultInjectionStore(context.TODO()) - store.createOrUpdateRule("global:deletePod", - conv.ConvertHTTPFaultInjection(limitingA)) - store.createOrUpdateRule("global:createDomain", - conv.ConvertHTTPFaultInjection(limitingB)) - - rules, states := store.byIndex(IndexResource, "*::pod:delete") - g := gomega.NewGomegaWithT(t) - g.Expect(len(rules)).To(gomega.BeEquivalentTo(1)) - // g.Expect(len(limiters)).To(gomega.BeEquivalentTo(1)) - g.Expect(len(states)).To(gomega.BeEquivalentTo(0)) - - rules, states = store.byIndex(IndexRest, "https://localhost:80/createDomain:POST") - g.Expect(len(rules)).To(gomega.BeEquivalentTo(1)) - // g.Expect(len(limiters)).To(gomega.BeEquivalentTo(1)) - g.Expect(len(states)).To(gomega.BeEquivalentTo(0)) -} - -func TestLimiterPriority(t *testing.T) { - fmt.Println("TestLimiterPriority") - g := gomega.NewGomegaWithT(t) - limitingA := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "deletePod-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "100", - FixedDelay: "2s", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "0", - }, - Match: &ctrlmeshv1alpha1.Match{ - Resources: []*ctrlmeshv1alpha1.ResourceMatch{ - { - Namespaces: []string{"*"}, - ApiGroups: []string{"*"}, - Resources: []string{"*"}, - Verbs: []string{"delete"}, - }, - }, - }, - } - - ctx := context.TODO() - mgr := &manager{ - faultInjectionMap: map[string]*ctrlmeshproto.FaultInjection{}, - faultInjectionStore: newFaultInjectionStore(ctx), - } - mgr.faultInjectionStore.createOrUpdateRule("global:deletePod-priority", - conv.ConvertHTTPFaultInjection(limitingA)) - - result := mgr.FaultInjectionResource("default", "", "pod", "delete") - g.Expect(result.Abort).Should(gomega.BeTrue()) - - limitingB := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "deletePod1-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "100", - FixedDelay: "2s", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - Resources: []*ctrlmeshv1alpha1.ResourceMatch{ - { - Namespaces: []string{"*"}, - ApiGroups: []string{"*"}, - Resources: []string{"pod"}, - Verbs: []string{"delete"}, - }, - }, - }, - } - - mgr.faultInjectionStore.createOrUpdateRule("global:deletePod1-priority", - conv.ConvertHTTPFaultInjection(limitingB)) - - result = mgr.FaultInjectionResource("default", "", "pod", "delete") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("deletePod1-priority")) - - limitingC := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "deletePod2-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - Resources: []*ctrlmeshv1alpha1.ResourceMatch{ - { - Namespaces: []string{"*"}, - ApiGroups: []string{""}, - Resources: []string{"pod"}, - Verbs: []string{"delete"}, - }, - }, - }, - } - mgr.faultInjectionStore.createOrUpdateRule("global:deletePod2-priority", - conv.ConvertHTTPFaultInjection(limitingC)) - result = mgr.FaultInjectionResource("default", "", "pod", "delete") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("deletePod2-priority")) - - limitingD := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "deletePod3-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - Resources: []*ctrlmeshv1alpha1.ResourceMatch{ - { - Namespaces: []string{"default"}, - ApiGroups: []string{""}, - Resources: []string{"pod"}, - Verbs: []string{"delete"}, - }, - }, - }, - } - mgr.faultInjectionStore.createOrUpdateRule("global:deletePod3-priority", - conv.ConvertHTTPFaultInjection(limitingD)) - result = mgr.FaultInjectionResource("default", "", "pod", "delete") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("deletePod3-priority")) -} - -func TestGenerateWildcardSeeds(t *testing.T) { - fmt.Println("TestGenerateWildcardSeeds") - g := gomega.NewGomegaWithT(t) - urls := generateWildcardUrls("https://www.baidu.com/", "GET") - g.Expect(len(urls) == 1).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("https://www.baidu.com/:GET")) - - urls = generateWildcardUrls("https://www.cnblogs.com/9854932.html", "GET") - g.Expect(len(urls) == 2).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/9854932.html:GET")) - g.Expect(urls[1]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/*:GET")) - - urls = generateWildcardUrls("http://localhost:8080/SpringMVCLesson/helloworld/detail/123", "POST") - g.Expect(len(urls) == 5).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("http://localhost:8080/SpringMVCLesson/helloworld/detail/123:POST")) - g.Expect(urls[1]).Should(gomega.BeEquivalentTo("http://localhost:8080/SpringMVCLesson/helloworld/detail/*:POST")) - g.Expect(urls[2]).Should(gomega.BeEquivalentTo("http://localhost:8080/SpringMVCLesson/helloworld/*:POST")) - g.Expect(urls[3]).Should(gomega.BeEquivalentTo("http://localhost:8080/SpringMVCLesson/*:POST")) - g.Expect(urls[4]).Should(gomega.BeEquivalentTo("http://localhost:8080/*:POST")) - - urls = generateWildcardUrls("http://localhost:8080", "POST") - g.Expect(len(urls) == 1).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("http://localhost:8080:POST")) - - urls = generateWildcardUrls("http://localhost", "POST") - g.Expect(len(urls) == 1).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("http://localhost:POST")) - - urls = generateWildcardUrls("https://www.cnblogs.com/f-ck-need-u/p/9854932.html", "GET") - g.Expect(len(urls) == 4).Should(gomega.BeTrue()) - g.Expect(urls[0]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/f-ck-need-u/p/9854932.html:GET")) - g.Expect(urls[1]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/f-ck-need-u/p/*:GET")) - g.Expect(urls[2]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/f-ck-need-u/*:GET")) - g.Expect(urls[3]).Should(gomega.BeEquivalentTo("https://www.cnblogs.com/*:GET")) -} - -func TestRestLimiterPriority(t *testing.T) { - fmt.Println("TestRestLimiterPriority") - limitingA := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "rest-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ - { - URL: []string{"https://www.cnblogs.com/*"}, - Method: []string{"GET"}, - }, - }, - }, - } - ctx := context.TODO() - mgr := &manager{ - faultInjectionMap: map[string]*ctrlmeshproto.FaultInjection{}, - faultInjectionStore: newFaultInjectionStore(ctx), - } - mgr.faultInjectionStore.createOrUpdateRule("global:rest-priority", - conv.ConvertHTTPFaultInjection(limitingA)) - g := gomega.NewGomegaWithT(t) - result := mgr.FaultInjectionRest("https://www.cnblogs.com/f-ck-need-u/p/9854932.html", "GET") - g.Expect(result.Abort).Should(gomega.BeTrue()) - - limitingB := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "rest1-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ - { - URL: []string{"https://www.cnblogs.com/f-ck-need-u/*"}, - Method: []string{"GET"}, - }, - }, - }, - } - mgr.faultInjectionStore.createOrUpdateRule("global:rest1-priority", - conv.ConvertHTTPFaultInjection(limitingB)) - result = mgr.FaultInjectionRest("https://www.cnblogs.com/f-ck-need-u/p/9854932.html", "GET") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("rest1-priority")) - - limitingC := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "rest2-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ - { - URL: []string{"https://www.cnblogs.com/f-ck-need-u/p/*"}, - Method: []string{"GET"}, - }, - }, - }, - } - - mgr.faultInjectionStore.createOrUpdateRule("global:rest2-priority", - conv.ConvertHTTPFaultInjection(limitingC)) - result = mgr.FaultInjectionRest("https://www.cnblogs.com/f-ck-need-u/p/9854932.html", "GET") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("rest2-priority")) - - limitingD := &ctrlmeshv1alpha1.HTTPFaultInjection{ - Name: "rest3-priority", - Delay: &ctrlmeshv1alpha1.HTTPFaultInjectionDelay{ - Percent: "32", - FixedDelay: "10ms", - }, - Abort: &ctrlmeshv1alpha1.HTTPFaultInjectionAbort{ - HttpStatus: 497, - Percent: "100", - }, - Match: &ctrlmeshv1alpha1.Match{ - HttpMatch: []*ctrlmeshv1alpha1.HttpMatch{ - { - URL: []string{"https://www.cnblogs.com/f-ck-need-u/p/9854932.html"}, - Method: []string{"GET"}, - }, - }, - }, - } - mgr.faultInjectionStore.createOrUpdateRule("global:rest3-priority", - conv.ConvertHTTPFaultInjection(limitingD)) - result = mgr.FaultInjectionRest("https://www.cnblogs.com/f-ck-need-u/p/9854932.html", "GET") - g.Expect(result.Abort).Should(gomega.BeTrue()) - g.Expect(result.Message).Should(gomega.ContainSubstring("rest3-priority")) -} diff --git a/pkg/proxy/http/http_proxy.go b/pkg/proxy/http/http_proxy.go index dbc4162..3ae4033 100644 --- a/pkg/proxy/http/http_proxy.go +++ b/pkg/proxy/http/http_proxy.go @@ -17,7 +17,6 @@ limitations under the License. package http import ( - "encoding/json" "fmt" "os" @@ -32,7 +31,6 @@ import ( meshhttp "github.com/KusionStack/controller-mesh/pkg/apis/ctrlmesh/http" "github.com/KusionStack/controller-mesh/pkg/proxy/circuitbreaker" "github.com/KusionStack/controller-mesh/pkg/proxy/faultinjection" - "github.com/KusionStack/controller-mesh/pkg/utils" utilshttp "github.com/KusionStack/controller-mesh/pkg/utils/http" ) @@ -89,34 +87,10 @@ func (t *tproxy) handleHTTP(resp http.ResponseWriter, req *http.Request) { klog.Infof("handel http request, url: %s ", realEndPointUrl.String()) // faultinjection if enableRestFaultInjection { - result := t.FaultInjector.FaultInjectionRest(realEp, 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 - } + injector := t.FaultInjector.GetInjectorByUrl(realEndPointUrl, req.Method) + if injector.Do(resp, req) { + klog.Infof("fault injected in %s", realEndPointUrl.String()) + return } }