diff --git a/pkg/apis/lbaas/v1/acl_genclient.go b/pkg/apis/lbaas/v1/acl_genclient.go new file mode 100644 index 00000000..a9e09612 --- /dev/null +++ b/pkg/apis/lbaas/v1/acl_genclient.go @@ -0,0 +1,58 @@ +package v1 + +import ( + "context" + "net/url" + + "go.anx.io/go-anxcloud/pkg/api/types" +) + +// EndpointURL returns the URL where to retrieve objects of type ACL and the identifier of the given ACL. +// It implements the api.Object interface on *ACL, making it usable with the generic API client. +func (a *ACL) EndpointURL(ctx context.Context) (*url.URL, error) { + op, err := types.OperationFromContext(ctx) + if err != nil { + return nil, err + } + + u, err := url.Parse("/api/LBaaS/v1/ACL.json") + if err != nil { + return nil, err + } + + if op == types.OperationList { + filters := make(url.Values) + + if a.ParentType != "" { + filters.Add("parent_type", a.ParentType) + } + if a.Backend.Identifier != "" { + filters.Add("backend", a.Backend.Identifier) + } + if a.Frontend.Identifier != "" { + filters.Add("frontend", a.Frontend.Identifier) + } + + query := u.Query() + query.Add("filters", filters.Encode()) + u.RawQuery = query.Encode() + } + + return u, nil +} + +// FilterAPIRequestBody generates the request body for ACLs, replacing linked Objects with just their identifier. +func (a *ACL) FilterAPIRequestBody(ctx context.Context) (interface{}, error) { + return requestBody(ctx, func() interface{} { + return &struct { + commonRequestBody + ACL + Backend string `json:"backend,omitempty"` + Frontend string `json:"frontend,omitempty"` + }{ + ACL: *a, + Backend: a.Backend.Identifier, + Frontend: a.Frontend.Identifier, + } + }) +} diff --git a/pkg/apis/lbaas/v1/acl_types.go b/pkg/apis/lbaas/v1/acl_types.go new file mode 100644 index 00000000..cdf4474e --- /dev/null +++ b/pkg/apis/lbaas/v1/acl_types.go @@ -0,0 +1,24 @@ +package v1 + +// anxcloud:object:hooks=RequestBodyHook + +// ACL represents an LBaaS ACL +type ACL struct { + commonMethods + HasState + + CustomerIdentifier string `json:"customer_identifier"` + ResellerIdentifier string `json:"reseller_identifier"` + + Identifier string `json:"identifier" anxcloud:"identifier"` + Name string `json:"name"` + ParentType string `json:"parent_type"` + Criterion string `json:"criterion"` + Value string `json:"value"` + Index int `json:"index"` + AutomationRules []RuleInfo `json:"automation_rules,omitempty"` + + // Only the name and identifier fields are used and returned. + Frontend Frontend `json:"frontend,omitempty"` + Backend Backend `json:"backend,omitempty"` +} diff --git a/pkg/apis/lbaas/v1/e2e_connection_test.go b/pkg/apis/lbaas/v1/e2e_connection_test.go index f40487b8..f48ea054 100644 --- a/pkg/apis/lbaas/v1/e2e_connection_test.go +++ b/pkg/apis/lbaas/v1/e2e_connection_test.go @@ -6,6 +6,7 @@ package v1 import ( "io/ioutil" "net/http" + "syscall" "time" . "github.com/onsi/ginkgo/v2" @@ -42,3 +43,10 @@ func unavailableServerConnectionCheck(url string) { }, 5*time.Second, 1*time.Second).Should(Succeed()) }) } + +func connectionResetByPeerCheck(url string) { + It("resets connection", func() { + _, err := http.Get(url) + Expect(err).To(MatchError(syscall.ECONNRESET)) + }) +} diff --git a/pkg/apis/lbaas/v1/e2e_test.go b/pkg/apis/lbaas/v1/e2e_test.go index d3ae8770..51f7913b 100644 --- a/pkg/apis/lbaas/v1/e2e_test.go +++ b/pkg/apis/lbaas/v1/e2e_test.go @@ -41,7 +41,59 @@ import ( // Maybe we can extract some of those e2e helpers for use by other API bindings? // -- Mara @LittleFox94 Grosch, 2022-05-03 -func serverChecks(testrun LBaaSE2ETestRun, backend *Backend) { +func ruleChecks(testrun LBaaSE2ETestRun, frontend *Frontend, acl *ACL, testURL string) { + Context("with a fresh Rule", Ordered, func() { + var rule Rule + + defer createObject(func() types.Object { + rule = Rule{ + Name: fmt.Sprintf("go-anxcloud-%s", testrun.Name), + ParentType: "frontend", + Index: 0, + Frontend: *frontend, + Condition: "if", + ConditionTest: acl.Name, + Type: "connection", + Action: "reject", + } + return &rule + }, true)() + + Context("rule blocks port", func() { + connectionResetByPeerCheck(testURL) + }) + + Context("rule allows port", func() { + updateObject(func() types.Object { + rule.Action = "accept" + return &rule + }, true) + successfulConnectionCheck(testURL) + }) + }) +} + +func aclChecks(testrun LBaaSE2ETestRun, frontend *Frontend, testURL string) { + Context("with a fresh ACL", Ordered, func() { + var acl ACL + + defer createObject(func() types.Object { + acl = ACL{ + Name: fmt.Sprintf("go-anxcloud-%s", testrun.Name), + ParentType: "frontend", + Index: 0, + Criterion: "dst_port", + Value: fmt.Sprintf("%d", testrun.Port), + Frontend: *frontend, + } + return &acl + }, true)() + + ruleChecks(testrun, frontend, &acl, testURL) + }) +} + +func serverChecks(testrun LBaaSE2ETestRun, backend *Backend, frontend *Frontend) { Context("with a fresh Server", Ordered, func() { var server Server @@ -62,6 +114,8 @@ func serverChecks(testrun LBaaSE2ETestRun, backend *Backend) { successfulConnectionCheck(url) }) + aclChecks(testrun, frontend, url) + Context("invalid server port", Ordered, func() { updateObject(func() types.Object { server.Port = 8081 @@ -86,7 +140,7 @@ func bindChecks(testrun LBaaSE2ETestRun, frontend *Frontend, backend *Backend) { return &bind }, true)() - serverChecks(testrun, backend) + serverChecks(testrun, backend, frontend) updateObject(func() types.Object { bind.Port = testrun.Port + 1 diff --git a/pkg/apis/lbaas/v1/rule_genclient.go b/pkg/apis/lbaas/v1/rule_genclient.go new file mode 100644 index 00000000..e43464aa --- /dev/null +++ b/pkg/apis/lbaas/v1/rule_genclient.go @@ -0,0 +1,76 @@ +package v1 + +import ( + "context" + "net/url" + + "go.anx.io/go-anxcloud/pkg/api/types" +) + +// EndpointURL returns the URL where to retrieve objects of type Rule and the identifier of the given Rule. +// It implements the api.Object interface on *Rule, making it usable with the generic API client. +func (r *Rule) EndpointURL(ctx context.Context) (*url.URL, error) { + op, err := types.OperationFromContext(ctx) + if err != nil { + return nil, err + } + + u, err := url.Parse("/api/LBaaS/v1/rule.json") + if err != nil { + return nil, err + } + + if op == types.OperationList { + filters := make(url.Values) + + if r.RuleType != "" { + filters.Add("rule_type", r.RuleType) + } + if r.ParentType != "" { + filters.Add("parent_type", r.ParentType) + } + if r.Frontend.Identifier != "" { + filters.Add("frontend", r.Frontend.Identifier) + } + if r.Backend.Identifier != "" { + filters.Add("backend", r.Backend.Identifier) + } + if r.Condition != "" { + filters.Add("condition", r.Condition) + } + if r.Type != "" { + filters.Add("type", r.Type) + } + if r.Action != "" { + filters.Add("action", r.Action) + } + if r.RedirectionType != "" { + filters.Add("redirection_type", r.RedirectionType) + } + if r.RedirectionCode != "" { + filters.Add("redirection_code", r.RedirectionCode) + } + + query := u.Query() + query.Add("filters", filters.Encode()) + u.RawQuery = query.Encode() + } + + return u, nil +} + +// FilterAPIRequestBody generates the request body for Rules, replacing linked Objects with just their identifier. +func (r *Rule) FilterAPIRequestBody(ctx context.Context) (interface{}, error) { + return requestBody(ctx, func() interface{} { + return &struct { + commonRequestBody + Rule + Backend string `json:"backend,omitempty"` + Frontend string `json:"frontend,omitempty"` + }{ + Rule: *r, + Backend: r.Backend.Identifier, + Frontend: r.Frontend.Identifier, + } + }) +} diff --git a/pkg/apis/lbaas/v1/rule_types.go b/pkg/apis/lbaas/v1/rule_types.go new file mode 100644 index 00000000..fe8f1cd6 --- /dev/null +++ b/pkg/apis/lbaas/v1/rule_types.go @@ -0,0 +1,29 @@ +package v1 + +// anxcloud:object:hooks=RequestBodyHook + +// Rule represents an LBaaS Rule +type Rule struct { + commonMethods + HasState + + CustomerIdentifier string `json:"customer_identifier"` + ResellerIdentifier string `json:"reseller_identifier"` + + Identifier string `json:"identifier" anxcloud:"identifier"` + Name string `json:"name"` + ParentType string `json:"parent_type"` + Index int `json:"index"` + Condition string `json:"condition"` + ConditionTest string `json:"condition_test"` + Type string `json:"type"` + Action string `json:"action"` + RedirectionType string `json:"redirection_type"` + RedirectionValue string `json:"redirection_value"` + RedirectionCode string `json:"redirection_code"` + RuleType string `json:"rule_type"` + + // Only the name and identifier fields are used and returned. + Frontend Frontend `json:"frontend,omitempty"` + Backend Backend `json:"backend,omitempty"` +} diff --git a/pkg/apis/lbaas/v1/xxgenerated_object_test.go b/pkg/apis/lbaas/v1/xxgenerated_object_test.go index d951fd93..897f386f 100644 --- a/pkg/apis/lbaas/v1/xxgenerated_object_test.go +++ b/pkg/apis/lbaas/v1/xxgenerated_object_test.go @@ -7,6 +7,22 @@ import ( "go.anx.io/go-anxcloud/pkg/api/types" ) +var _ = Describe("Object ACL", func() { + o := ACL{} + + ifaces := make([]interface{}, 0, 2) + { + var i types.Object + ifaces = append(ifaces, &i) + } + { + var i types.RequestBodyHook + ifaces = append(ifaces, &i) + } + + testutils.ObjectTests(&o, ifaces...) +}) + var _ = Describe("Object Backend", func() { o := Backend{} @@ -75,6 +91,22 @@ var _ = Describe("Object LoadBalancer", func() { testutils.ObjectTests(&o, ifaces...) }) +var _ = Describe("Object Rule", func() { + o := Rule{} + + ifaces := make([]interface{}, 0, 2) + { + var i types.Object + ifaces = append(ifaces, &i) + } + { + var i types.RequestBodyHook + ifaces = append(ifaces, &i) + } + + testutils.ObjectTests(&o, ifaces...) +}) + var _ = Describe("Object Server", func() { o := Server{}