Skip to content

Commit

Permalink
Create httpsoIndex and httpsoStore
Browse files Browse the repository at this point in the history
  • Loading branch information
Erich Shan committed Dec 22, 2024
1 parent 94bf27f commit b1b19ad
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 159 deletions.
8 changes: 7 additions & 1 deletion operator/apis/http/v1alpha1/httpscaledobject_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,13 @@ type HTTPScaledObject struct {
type HTTPScaledObjectList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []HTTPScaledObject `json:"items"`
Items []*HTTPScaledObject `json:"items"`
}

func NewHTTPScaledObjectList(httpScaledObjects []*HTTPScaledObject) *HTTPScaledObjectList {
return &HTTPScaledObjectList{
Items: httpScaledObjects,
}
}

func init() {
Expand Down
4 changes: 2 additions & 2 deletions operator/apis/http/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions pkg/routing/httpso_index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package routing

import (
iradix "github.com/hashicorp/go-immutable-radix/v2"
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
)

type httpSOIndex struct {
radix *iradix.Tree[*httpv1alpha1.HTTPScaledObject]
}

func newHTTPSOIndex() *httpSOIndex {
return &httpSOIndex{radix: iradix.New[*httpv1alpha1.HTTPScaledObject]()}
}

func (hi *httpSOIndex) insert(key tableMemoryIndexKey, httpso *httpv1alpha1.HTTPScaledObject) (*httpSOIndex, *httpv1alpha1.HTTPScaledObject, bool) {
newRadix, oldVal, ok := hi.radix.Insert(key, httpso)
newHttpSOIndex := &httpSOIndex{
radix: newRadix,
}
return newHttpSOIndex, oldVal, ok
}

func (hi *httpSOIndex) get(key tableMemoryIndexKey) (*httpv1alpha1.HTTPScaledObject, bool) {
return hi.radix.Get(key)
}

func (hi *httpSOIndex) delete(key tableMemoryIndexKey) (*httpSOIndex, *httpv1alpha1.HTTPScaledObject) {
newRadix, oldHttpSO, _ := hi.radix.Delete(key)
newHttpSOIndex := &httpSOIndex{
radix: newRadix,
}
return newHttpSOIndex, oldHttpSO
}
150 changes: 150 additions & 0 deletions pkg/routing/httpso_index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package routing

import (
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
"github.com/kedacore/http-add-on/pkg/k8s"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("httpSOIndex", func() {
var (
httpso0 = &httpv1alpha1.HTTPScaledObject{
ObjectMeta: metav1.ObjectMeta{
Name: "keda-sh",
},
Spec: httpv1alpha1.HTTPScaledObjectSpec{
Hosts: []string{
"keda.sh",
},
},
}

httpso0NamespacedName = k8s.NamespacedNameFromObject(httpso0)
httpso0IndexKey = newTableMemoryIndexKey(httpso0NamespacedName)

httpso1 = &httpv1alpha1.HTTPScaledObject{
ObjectMeta: metav1.ObjectMeta{
Name: "one-one-one-one",
},
Spec: httpv1alpha1.HTTPScaledObjectSpec{
Hosts: []string{
"1.1.1.1",
},
},
}
httpso1NamespacedName = k8s.NamespacedNameFromObject(httpso1)
httpso1IndexKey = newTableMemoryIndexKey(httpso1NamespacedName)
)
Context("New", func() {
It("returns a httpSOIndex with initialized tree", func() {
index := newHTTPSOIndex()
Expect(index.radix).NotTo(BeNil())
})
})

Context("Get / Insert", func() {
It("Get on empty httpSOIndex returns nil", func() {
index := newHTTPSOIndex()
_, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeFalse())
})
It("httpSOIndex insert will return previous object if set", func() {
index := newHTTPSOIndex()
index, prevVal, prevSet := index.insert(httpso0IndexKey, httpso0)
Expect(prevSet).To(BeFalse())
Expect(prevVal).To(BeNil())
httpso0Copy := httpso0.DeepCopy()
httpso0Copy.Name = "httpso0Copy"
index, prevVal, prevSet = index.insert(httpso0IndexKey, httpso0Copy)
Expect(prevSet).To(BeTrue())
Expect(prevVal).To(Equal(httpso0))
Expect(prevVal).ToNot(Equal(httpso0Copy))
httpso, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).ToNot(Equal(httpso0))
Expect(httpso).To(Equal(httpso0Copy))
})

It("httpSOIndex with new object inserted returns object", func() {
index := newHTTPSOIndex()
index, httpso, prevSet := index.insert(httpso0IndexKey, httpso0)
Expect(prevSet).To(BeFalse())
Expect(httpso).To(BeNil())
httpso, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))
})

It("httpSOIndex with new object inserted retains other object", func() {
index := newHTTPSOIndex()

index, _, _ = index.insert(httpso0IndexKey, httpso0)
httpso, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))

_, ok = index.get(httpso1IndexKey)
Expect(ok).To(BeFalse())

index, _, _ = index.insert(httpso1IndexKey, httpso1)
httpso, ok = index.get(httpso1IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso1))

// httpso0 still there
httpso, ok = index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))
})
})

Context("Get / Delete", func() {
It("delete on empty httpSOIndex returns nil", func() {
index := newHTTPSOIndex()
_, httpso := index.delete(httpso0IndexKey)
Expect(httpso).To(BeNil())
})

It("double delete returns nil the second time", func() {
index := newHTTPSOIndex()
index, _, _ = index.insert(httpso0IndexKey, httpso0)
index, _, _ = index.insert(httpso1IndexKey, httpso1)
index, deletedVal := index.delete(httpso0IndexKey)
Expect(deletedVal).To(Equal(httpso0))
index, deletedVal = index.delete(httpso0IndexKey)
Expect(deletedVal).To(BeNil())
})

It("delete on httpSOIndex removes object ", func() {
index := newHTTPSOIndex()
index, _, _ = index.insert(httpso0IndexKey, httpso0)
httpso, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))
index, deletedVal := index.delete(httpso0IndexKey)
Expect(deletedVal).To(Equal(httpso0))
httpso, ok = index.get(httpso0IndexKey)
Expect(httpso).To(BeNil())
Expect(ok).To(BeFalse())
})
It("httpSOIndex delete on one object does not affect other", func() {
index := newHTTPSOIndex()

index, _, _ = index.insert(httpso0IndexKey, httpso0)
index, _, _ = index.insert(httpso1IndexKey, httpso1)
httpso, ok := index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))
index, deletedVal := index.delete(httpso1IndexKey)
Expect(deletedVal).To(Equal(httpso1))
httpso, ok = index.get(httpso0IndexKey)
Expect(ok).To(BeTrue())
Expect(httpso).To(Equal(httpso0))
httpso, ok = index.get(httpso1IndexKey)
Expect(ok).To(BeFalse())
Expect(httpso).To(BeNil())
})
})
})
92 changes: 92 additions & 0 deletions pkg/routing/httpso_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package routing

import (
iradix "github.com/hashicorp/go-immutable-radix/v2"
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
"github.com/kedacore/http-add-on/pkg/k8s"
)

// light wrapper around radix tree containing HTTPScaledObjectList
// with convenience functions to manage CRUD for individual HTTPScaledObject.
// created as an abstraction to manage complexity for tablememory implementation
// the store is meant to map host + path keys to one or more HTTPScaledObject
// and return one arbitrarily or route based on headers
type httpSOStore struct {
radix *iradix.Tree[*httpv1alpha1.HTTPScaledObjectList]
}

func newHTTPSOStore() *httpSOStore {
return &httpSOStore{radix: iradix.New[*httpv1alpha1.HTTPScaledObjectList]()}
}

// Insert key value into httpSOStore
// Gets old list of HTTPScaledObjectList
// if exists appends to list and returns new httpSOStore
// with new radix tree
func (hs *httpSOStore) append(key Key, httpso *httpv1alpha1.HTTPScaledObject) *httpSOStore {
httpsoList, found := hs.radix.Get(key)
var newHttpSOStore *httpSOStore
if !found {
newList := &httpv1alpha1.HTTPScaledObjectList{Items: []*httpv1alpha1.HTTPScaledObject{httpso}}
newRadix, _, _ := hs.radix.Insert(key, newList)
newHttpSOStore = &httpSOStore{
radix: newRadix,
}
} else {
newList := &httpv1alpha1.HTTPScaledObjectList{Items: append(httpsoList.Items, httpso)}
newRadix, _, _ := hs.radix.Insert(key, newList)
newHttpSOStore = &httpSOStore{
radix: newRadix,
}
}
return newHttpSOStore
}

func (hs *httpSOStore) insert(key Key, httpsoList *httpv1alpha1.HTTPScaledObjectList) (*httpSOStore, *httpv1alpha1.HTTPScaledObjectList, bool) {
newRadix, oldVal, ok := hs.radix.Insert(key, httpsoList)
newHttpSOStore := &httpSOStore{
radix: newRadix,
}
return newHttpSOStore, oldVal, ok
}

func (hs *httpSOStore) get(key Key) (*httpv1alpha1.HTTPScaledObjectList, bool) {
return hs.radix.Get(key)
}

func (hs *httpSOStore) delete(key Key) *httpSOStore {
newRadix, _, _ := hs.radix.Delete(key)
newHttpSOStore := &httpSOStore{
radix: newRadix,
}
return newHttpSOStore
}

// convenience function
// retrieves all keys associated with HTTPScaledObject
// and deletes it from every list in the store
func (hs *httpSOStore) DeleteAllInstancesOfHTTPSO(httpso *httpv1alpha1.HTTPScaledObject) *httpSOStore {
httpsoNamespacedName := k8s.NamespacedNameFromObject(httpso)
newHttpSOStore := &httpSOStore{radix: hs.radix}
keys := NewKeysFromHTTPSO(httpso)
for _, key := range keys {
httpsoList, _ := newHttpSOStore.radix.Get(key)
for i, httpso := range httpsoList.Items {
// delete only if namespaced names match
if currHttpsoNamespacedName := k8s.NamespacedNameFromObject(httpso); *httpsoNamespacedName == *currHttpsoNamespacedName {
httpsoList.Items = append(httpsoList.Items[:i], httpsoList.Items[i+1:]...)
break
}
}
if len(httpsoList.Items) == 0 {
newHttpSOStore.radix, _, _ = newHttpSOStore.radix.Delete(key)
} else {
newHttpSOStore.radix, _, _ = newHttpSOStore.radix.Insert(key, httpsoList)
}
}
return newHttpSOStore
}

func (hs *httpSOStore) GetLongestPrefix(key Key) ([]byte, *httpv1alpha1.HTTPScaledObjectList, bool) {
return hs.radix.Root().LongestPrefix(key)
}
2 changes: 1 addition & 1 deletion pkg/routing/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (t *table) Route(req *http.Request) *httpv1alpha1.HTTPScaledObject {
}

key := NewKeyFromRequest(req)
return tm.Route(key, req.Header)
return tm.RouteWithHeaders(key, req.Header)
}

func (t *table) HasSynced() bool {
Expand Down
Loading

0 comments on commit b1b19ad

Please sign in to comment.