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 23, 2024
1 parent 94bf27f commit f63b75a
Show file tree
Hide file tree
Showing 10 changed files with 618 additions and 160 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, oldSet := hi.radix.Insert(key, httpso)
newHttpSOIndex := &httpSOIndex{
radix: newRadix,
}
return newHttpSOIndex, oldVal, oldSet
}

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

func (hi *httpSOIndex) delete(key tableMemoryIndexKey) (*httpSOIndex, *httpv1alpha1.HTTPScaledObject, bool) {
newRadix, oldVal, oldSet := hi.radix.Delete(key)
newHttpSOIndex := &httpSOIndex{
radix: newRadix,
}
return newHttpSOIndex, oldVal, oldSet
}
156 changes: 156 additions & 0 deletions pkg/routing/httpso_index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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, oldSet := index.delete(httpso0IndexKey)
Expect(httpso).To(BeNil())
Expect(oldSet).To(BeFalse())
})

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

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, oldSet := index.delete(httpso0IndexKey)
Expect(deletedVal).To(Equal(httpso0))
Expect(oldSet).To(BeTrue())
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, oldSet := index.delete(httpso1IndexKey)
Expect(deletedVal).To(Equal(httpso1))
Expect(oldSet).To(BeTrue())
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, *httpv1alpha1.HTTPScaledObjectList, bool) {
newRadix, oldVal, oldSet := hs.radix.Delete(key)
newHttpSOStore := &httpSOStore{
radix: newRadix,
}
return newHttpSOStore, oldVal, oldSet
}

// 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)
}
Loading

0 comments on commit f63b75a

Please sign in to comment.