Skip to content

Commit b1b19ad

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

File tree

9 files changed

+378
-159
lines changed

9 files changed

+378
-159
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, ok := hi.radix.Insert(key, httpso)
18+
newHttpSOIndex := &httpSOIndex{
19+
radix: newRadix,
20+
}
21+
return newHttpSOIndex, oldVal, ok
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) {
29+
newRadix, oldHttpSO, _ := hi.radix.Delete(key)
30+
newHttpSOIndex := &httpSOIndex{
31+
radix: newRadix,
32+
}
33+
return newHttpSOIndex, oldHttpSO
34+
}

pkg/routing/httpso_index_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 := index.delete(httpso0IndexKey)
107+
Expect(httpso).To(BeNil())
108+
})
109+
110+
It("double delete returns nil the second time", func() {
111+
index := newHTTPSOIndex()
112+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
113+
index, _, _ = index.insert(httpso1IndexKey, httpso1)
114+
index, deletedVal := index.delete(httpso0IndexKey)
115+
Expect(deletedVal).To(Equal(httpso0))
116+
index, deletedVal = index.delete(httpso0IndexKey)
117+
Expect(deletedVal).To(BeNil())
118+
})
119+
120+
It("delete on httpSOIndex removes object ", func() {
121+
index := newHTTPSOIndex()
122+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
123+
httpso, ok := index.get(httpso0IndexKey)
124+
Expect(ok).To(BeTrue())
125+
Expect(httpso).To(Equal(httpso0))
126+
index, deletedVal := index.delete(httpso0IndexKey)
127+
Expect(deletedVal).To(Equal(httpso0))
128+
httpso, ok = index.get(httpso0IndexKey)
129+
Expect(httpso).To(BeNil())
130+
Expect(ok).To(BeFalse())
131+
})
132+
It("httpSOIndex delete on one object does not affect other", func() {
133+
index := newHTTPSOIndex()
134+
135+
index, _, _ = index.insert(httpso0IndexKey, httpso0)
136+
index, _, _ = index.insert(httpso1IndexKey, httpso1)
137+
httpso, ok := index.get(httpso0IndexKey)
138+
Expect(ok).To(BeTrue())
139+
Expect(httpso).To(Equal(httpso0))
140+
index, deletedVal := index.delete(httpso1IndexKey)
141+
Expect(deletedVal).To(Equal(httpso1))
142+
httpso, ok = index.get(httpso0IndexKey)
143+
Expect(ok).To(BeTrue())
144+
Expect(httpso).To(Equal(httpso0))
145+
httpso, ok = index.get(httpso1IndexKey)
146+
Expect(ok).To(BeFalse())
147+
Expect(httpso).To(BeNil())
148+
})
149+
})
150+
})

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 {
58+
newRadix, _, _ := hs.radix.Delete(key)
59+
newHttpSOStore := &httpSOStore{
60+
radix: newRadix,
61+
}
62+
return newHttpSOStore
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+
}

pkg/routing/table.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (t *table) Route(req *http.Request) *httpv1alpha1.HTTPScaledObject {
135135
}
136136

137137
key := NewKeyFromRequest(req)
138-
return tm.Route(key, req.Header)
138+
return tm.RouteWithHeaders(key, req.Header)
139139
}
140140

141141
func (t *table) HasSynced() bool {

0 commit comments

Comments
 (0)