feat(cluster): Add support for request proxying for scale out (#2385)

* feat(cluster): initial commit for scale-out cluster

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* feat(cluster): support shared storage scale out

This change introduces support for shared storage backed
zot cluster scale out.

New feature
Multiple stateless zot instances can run using the same shared
storage backend where each instance looks at a specific set
of repositories based on a siphash of the repository name to improve
scale as the load is distributed across multiple instances.
For a given config, there will only be one instance that can perform
dist-spec read/write on a given repository.

What's changed?
- introduced a transparent request proxy for dist-spec endpoints based on
siphash of repository name.
- new config for scale out cluster that specifies list of
cluster members.

Signed-off-by: Vishwas Rajashekar <vrajashe@cisco.com>

---------

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
Signed-off-by: Vishwas Rajashekar <vrajashe@cisco.com>
Co-authored-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
Vishwas R
2024-05-20 21:35:21 +05:30
committed by GitHub
parent be5ad66797
commit 5ae7a028d9
30 changed files with 2320 additions and 24 deletions
+59
View File
@@ -0,0 +1,59 @@
//go:build sync && scrub && metrics && search && lint && userprefs && mgmt && imagetrust && ui
// +build sync,scrub,metrics,search,lint,userprefs,mgmt,imagetrust,ui
package api_test
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/pkg/api"
)
func TestGetLocalMemberClusterSocket(t *testing.T) {
Convey("Should return an error if a domain name doesn't exist", t, func() {
localSockets := []string{"127.0.0.1:9000", "172.16.0.1:9000"}
members := []string{"127.0.0.1:9001", "thisdoesnotexist:9000", "127.0.0.1:9000"}
index, socket, err := api.GetLocalMemberClusterSocket(members, localSockets)
So(err.Error(), ShouldContainSubstring, "lookup thisdoesnotexist")
So(index, ShouldEqual, -1)
So(socket, ShouldEqual, "")
})
Convey("Should return an error if a local socket is missing a port", t, func() {
localSockets := []string{"127.0.0.1", "172.16.0.1:9000"}
members := []string{"127.0.0.1:9001", "www.github.com:443", "127.0.0.1:9000"}
index, socket, err := api.GetLocalMemberClusterSocket(members, localSockets)
So(err.Error(), ShouldEqual, "address 127.0.0.1: missing port in address")
So(index, ShouldEqual, -1)
So(socket, ShouldEqual, "")
})
Convey("Should return an error if a member socket is missing a port", t, func() {
localSockets := []string{"127.0.0.1:9000", "172.16.0.1:9000"}
members := []string{"127.0.0.1:9001", "www.github.com", "127.0.0.1:9000"}
index, socket, err := api.GetLocalMemberClusterSocket(members, localSockets)
So(err.Error(), ShouldEqual, "address www.github.com: missing port in address")
So(index, ShouldEqual, -1)
So(socket, ShouldEqual, "")
})
Convey("Should return the right socket when a local socket is part of members", t, func() {
localSockets := []string{"127.0.0.1:9000", "172.16.0.1:9000"}
members := []string{"127.0.0.1:9001", "www.github.com:443", "127.0.0.1:9000"}
index, socket, err := api.GetLocalMemberClusterSocket(members, localSockets)
So(err, ShouldBeNil)
So(index, ShouldEqual, 2)
So(socket, ShouldEqual, "127.0.0.1:9000")
})
Convey("Should return empty when no local socket is part of members", t, func() {
localSockets := []string{"127.0.0.1:9000", "172.16.0.1:9000"}
members := []string{"127.0.0.1:9002", "127.0.0.1:9001", "www.github.com:443"}
index, socket, err := api.GetLocalMemberClusterSocket(members, localSockets)
So(err, ShouldBeNil)
So(index, ShouldEqual, -1)
So(socket, ShouldBeEmpty)
})
}