Skip to content

Commit f63b75a

Browse files
author
Erich Shan
committed
Create httpsoIndex and httpsoStore
1 parent 94bf27f commit f63b75a

File tree

10 files changed

+618
-160
lines changed

10 files changed

+618
-160
lines changed

operator/apis/http/v1alpha1/httpscaledobject_types.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,13 @@ type HTTPScaledObject struct {
154154
type HTTPScaledObjectList struct {
155155
metav1.TypeMeta `json:",inline"`
156156
metav1.ListMeta `json:"metadata,omitempty"`
157-
Items []HTTPScaledObject `json:"items"`
157+
Items []*HTTPScaledObject `json:"items"`
158+
}
159+
160+
func NewHTTPScaledObjectList(httpScaledObjects []*HTTPScaledObject) *HTTPScaledObjectList {
161+
return &HTTPScaledObjectList{
162+
Items: httpScaledObjects,
163+
}
158164
}
159165

160166
func init() {

operator/apis/http/v1alpha1/zz_generated.deepcopy.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/routing/httpso_index.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package routing
2+
3+
import (
4+
iradix "github.com/hashicorp/go-immutable-radix/v2"
5+
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
6+
)
7+
8+
type httpSOIndex struct {
9+
radix *iradix.Tree[*httpv1alpha1.HTTPScaledObject]
10+
}
11+
12+
func newHTTPSOIndex() *httpSOIndex {
13+
return &httpSOIndex{radix: iradix.New[*httpv1alpha1.HTTPScaledObject]()}
14+
}
15+
16+
func (hi *httpSOIndex) insert(key tableMemoryIndexKey, httpso *httpv1alpha1.HTTPScaledObject) (*httpSOIndex, *httpv1alpha1.HTTPScaledObject, bool) {
17+
newRadix, oldVal, oldSet := hi.radix.Insert(key, httpso)
18+
newHttpSOIndex := &httpSOIndex{
19+
radix: newRadix,
20+
}
21+
return newHttpSOIndex, oldVal, oldSet
22+
}
23+
24+
func (hi *httpSOIndex) get(key tableMemoryIndexKey) (*httpv1alpha1.HTTPScaledObject, bool) {
25+
return hi.radix.Get(key)
26+
}
27+
28+
func (hi *httpSOIndex) delete(key tableMemoryIndexKey) (*httpSOIndex, *httpv1alpha1.HTTPScaledObject, bool) {
29+
newRadix, oldVal, oldSet := hi.radix.Delete(key)
30+
newHttpSOIndex := &httpSOIndex{
31+
radix: newRadix,
32+
}
33+
return newHttpSOIndex, oldVal, oldSet
34+
}

pkg/routing/httpso_index_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package routing
2+
3+
import (
4+
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
5+
"github.com/kedacore/http-add-on/pkg/k8s"
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
var _ = Describe("httpSOIndex", func() {
12+
var (
13+
httpso0 = &httpv1alpha1.HTTPScaledObject{
14+
ObjectMeta: metav1.ObjectMeta{
15+
Name: "keda-sh",
16+
},
17+
Spec: httpv1alpha1.HTTPScaledObjectSpec{
18+
Hosts: []string{
19+
"keda.sh",
20+
},
21+
},
22+
}
23+
24+
httpso0NamespacedName = k8s.NamespacedNameFromObject(httpso0)
25+
httpso0IndexKey = newTableMemoryIndexKey(httpso0NamespacedName)
26+
27+
httpso1 = &httpv1alpha1.HTTPScaledObject{
28+
ObjectMeta: metav1.ObjectMeta{
29+
Name: "one-one-one-one",
30+
},
31+
Spec: httpv1alpha1.HTTPScaledObjectSpec{
32+
Hosts: []string{
33+
"1.1.1.1",
34+
},
35+
},
36+
}
37+
httpso1NamespacedName = k8s.NamespacedNameFromObject(httpso1)
38+
httpso1IndexKey = newTableMemoryIndexKey(httpso1NamespacedName)
39+
)
40+
Context("New", func() {
41+
It("returns a httpSOIndex with initialized tree", func() {
42+
index := newHTTPSOIndex()
43+
Expect(index.radix).NotTo(BeNil())
44+
})
45+
})
46+
47+
Context("Get / Insert", func() {
48+
It("Get on empty httpSOIndex returns nil", func() {
49+
index := newHTTPSOIndex()
50+
_, ok := index.get(httpso0IndexKey)
51+
Expect(ok).To(BeFalse())
52+
})
53+
It("httpSOIndex insert will return previous object if set", func() {
54+
index := newHTTPSOIndex()
55+
index, prevVal, prevSet := index.insert(httpso0IndexKey, httpso0)
56+
Expect(prevSet).To(BeFalse())
57+
Expect(prevVal).To(BeNil())
58+
httpso0Copy := httpso0.DeepCopy()
59+
httpso0Copy.Name = "httpso0Copy"
60+
index, prevVal, prevSet = index.insert(httpso0IndexKey, httpso0Copy)
61+
Expect(prevSet).To(BeTrue())
62+
Expect(prevVal).To(Equal(httpso0))
63+
Expect(prevVal).ToNot(Equal(httpso0Copy))
64+
httpso, ok := index.get(httpso0IndexKey)
65+
Expect(ok).To(BeTrue())
66+
Expect(httpso).ToNot(Equal(httpso0))
67+
Expect(httpso).To(Equal(httpso0Copy))
68+
})
69+
70+
It("httpSOIndex with new object inserted returns object", func() {
71+
index := newHTTPSOIndex()
72+
index, httpso, prevSet := index.insert(httpso0IndexKey, httpso0)
73+
Expect(prevSet).To(BeFalse())
74+
Expect(httpso).To(BeNil())
75+
httpso, ok := index.get(httpso0IndexKey)
76+
Expect(ok).To(BeTrue())
77+
Expect(httpso).To(Equal(httpso0))
78+
})
79+
80+
It("httpSOIndex with new object inserted retains other object", func() {
81+
index := newHTTPSOIndex()
82+
83+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
84+
httpso, ok := index.get(httpso0IndexKey)
85+
Expect(ok).To(BeTrue())
86+
Expect(httpso).To(Equal(httpso0))
87+
88+
_, ok = index.get(httpso1IndexKey)
89+
Expect(ok).To(BeFalse())
90+
91+
index, _, _ = index.insert(httpso1IndexKey, httpso1)
92+
httpso, ok = index.get(httpso1IndexKey)
93+
Expect(ok).To(BeTrue())
94+
Expect(httpso).To(Equal(httpso1))
95+
96+
// httpso0 still there
97+
httpso, ok = index.get(httpso0IndexKey)
98+
Expect(ok).To(BeTrue())
99+
Expect(httpso).To(Equal(httpso0))
100+
})
101+
})
102+
103+
Context("Get / Delete", func() {
104+
It("delete on empty httpSOIndex returns nil", func() {
105+
index := newHTTPSOIndex()
106+
_, httpso, oldSet := index.delete(httpso0IndexKey)
107+
Expect(httpso).To(BeNil())
108+
Expect(oldSet).To(BeFalse())
109+
})
110+
111+
It("double delete returns nil the second time", func() {
112+
index := newHTTPSOIndex()
113+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
114+
index, _, _ = index.insert(httpso1IndexKey, httpso1)
115+
index, deletedVal, oldSet := index.delete(httpso0IndexKey)
116+
Expect(deletedVal).To(Equal(httpso0))
117+
Expect(oldSet).To(BeTrue())
118+
index, deletedVal, oldSet = index.delete(httpso0IndexKey)
119+
Expect(deletedVal).To(BeNil())
120+
Expect(oldSet).To(BeFalse())
121+
})
122+
123+
It("delete on httpSOIndex removes object ", func() {
124+
index := newHTTPSOIndex()
125+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
126+
httpso, ok := index.get(httpso0IndexKey)
127+
Expect(ok).To(BeTrue())
128+
Expect(httpso).To(Equal(httpso0))
129+
index, deletedVal, oldSet := index.delete(httpso0IndexKey)
130+
Expect(deletedVal).To(Equal(httpso0))
131+
Expect(oldSet).To(BeTrue())
132+
httpso, ok = index.get(httpso0IndexKey)
133+
Expect(httpso).To(BeNil())
134+
Expect(ok).To(BeFalse())
135+
})
136+
137+
It("httpSOIndex delete on one object does not affect other", func() {
138+
index := newHTTPSOIndex()
139+
140+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
141+
index, _, _ = index.insert(httpso1IndexKey, httpso1)
142+
httpso, ok := index.get(httpso0IndexKey)
143+
Expect(ok).To(BeTrue())
144+
Expect(httpso).To(Equal(httpso0))
145+
index, deletedVal, oldSet := index.delete(httpso1IndexKey)
146+
Expect(deletedVal).To(Equal(httpso1))
147+
Expect(oldSet).To(BeTrue())
148+
httpso, ok = index.get(httpso0IndexKey)
149+
Expect(ok).To(BeTrue())
150+
Expect(httpso).To(Equal(httpso0))
151+
httpso, ok = index.get(httpso1IndexKey)
152+
Expect(ok).To(BeFalse())
153+
Expect(httpso).To(BeNil())
154+
})
155+
})
156+
})

pkg/routing/httpso_store.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package routing
2+
3+
import (
4+
iradix "github.com/hashicorp/go-immutable-radix/v2"
5+
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
6+
"github.com/kedacore/http-add-on/pkg/k8s"
7+
)
8+
9+
// light wrapper around radix tree containing HTTPScaledObjectList
10+
// with convenience functions to manage CRUD for individual HTTPScaledObject.
11+
// created as an abstraction to manage complexity for tablememory implementation
12+
// the store is meant to map host + path keys to one or more HTTPScaledObject
13+
// and return one arbitrarily or route based on headers
14+
type httpSOStore struct {
15+
radix *iradix.Tree[*httpv1alpha1.HTTPScaledObjectList]
16+
}
17+
18+
func newHTTPSOStore() *httpSOStore {
19+
return &httpSOStore{radix: iradix.New[*httpv1alpha1.HTTPScaledObjectList]()}
20+
}
21+
22+
// Insert key value into httpSOStore
23+
// Gets old list of HTTPScaledObjectList
24+
// if exists appends to list and returns new httpSOStore
25+
// with new radix tree
26+
func (hs *httpSOStore) append(key Key, httpso *httpv1alpha1.HTTPScaledObject) *httpSOStore {
27+
httpsoList, found := hs.radix.Get(key)
28+
var newHttpSOStore *httpSOStore
29+
if !found {
30+
newList := &httpv1alpha1.HTTPScaledObjectList{Items: []*httpv1alpha1.HTTPScaledObject{httpso}}
31+
newRadix, _, _ := hs.radix.Insert(key, newList)
32+
newHttpSOStore = &httpSOStore{
33+
radix: newRadix,
34+
}
35+
} else {
36+
newList := &httpv1alpha1.HTTPScaledObjectList{Items: append(httpsoList.Items, httpso)}
37+
newRadix, _, _ := hs.radix.Insert(key, newList)
38+
newHttpSOStore = &httpSOStore{
39+
radix: newRadix,
40+
}
41+
}
42+
return newHttpSOStore
43+
}
44+
45+
func (hs *httpSOStore) insert(key Key, httpsoList *httpv1alpha1.HTTPScaledObjectList) (*httpSOStore, *httpv1alpha1.HTTPScaledObjectList, bool) {
46+
newRadix, oldVal, ok := hs.radix.Insert(key, httpsoList)
47+
newHttpSOStore := &httpSOStore{
48+
radix: newRadix,
49+
}
50+
return newHttpSOStore, oldVal, ok
51+
}
52+
53+
func (hs *httpSOStore) get(key Key) (*httpv1alpha1.HTTPScaledObjectList, bool) {
54+
return hs.radix.Get(key)
55+
}
56+
57+
func (hs *httpSOStore) delete(key Key) (*httpSOStore, *httpv1alpha1.HTTPScaledObjectList, bool) {
58+
newRadix, oldVal, oldSet := hs.radix.Delete(key)
59+
newHttpSOStore := &httpSOStore{
60+
radix: newRadix,
61+
}
62+
return newHttpSOStore, oldVal, oldSet
63+
}
64+
65+
// convenience function
66+
// retrieves all keys associated with HTTPScaledObject
67+
// and deletes it from every list in the store
68+
func (hs *httpSOStore) DeleteAllInstancesOfHTTPSO(httpso *httpv1alpha1.HTTPScaledObject) *httpSOStore {
69+
httpsoNamespacedName := k8s.NamespacedNameFromObject(httpso)
70+
newHttpSOStore := &httpSOStore{radix: hs.radix}
71+
keys := NewKeysFromHTTPSO(httpso)
72+
for _, key := range keys {
73+
httpsoList, _ := newHttpSOStore.radix.Get(key)
74+
for i, httpso := range httpsoList.Items {
75+
// delete only if namespaced names match
76+
if currHttpsoNamespacedName := k8s.NamespacedNameFromObject(httpso); *httpsoNamespacedName == *currHttpsoNamespacedName {
77+
httpsoList.Items = append(httpsoList.Items[:i], httpsoList.Items[i+1:]...)
78+
break
79+
}
80+
}
81+
if len(httpsoList.Items) == 0 {
82+
newHttpSOStore.radix, _, _ = newHttpSOStore.radix.Delete(key)
83+
} else {
84+
newHttpSOStore.radix, _, _ = newHttpSOStore.radix.Insert(key, httpsoList)
85+
}
86+
}
87+
return newHttpSOStore
88+
}
89+
90+
func (hs *httpSOStore) GetLongestPrefix(key Key) ([]byte, *httpv1alpha1.HTTPScaledObjectList, bool) {
91+
return hs.radix.Root().LongestPrefix(key)
92+
}

0 commit comments

Comments
 (0)