Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop the Extension API Server reference from agones-allocator #1124

Merged
merged 2 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 72 additions & 15 deletions cmd/allocator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,30 @@ import (
"os"
"path/filepath"
"strings"
"time"

"agones.dev/agones/pkg"
"agones.dev/agones/pkg/allocation/converters"
pb "agones.dev/agones/pkg/allocation/go/v1alpha1"
allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
"agones.dev/agones/pkg/client/clientset/versioned"
"agones.dev/agones/pkg/client/informers/externalversions"
"agones.dev/agones/pkg/gameserverallocations"
"agones.dev/agones/pkg/gameservers"
"agones.dev/agones/pkg/metrics"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
"github.com/heptiolabs/healthcheck"
prom "github.com/prometheus/client_golang/prometheus"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/stats/view"
k8serror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

Expand Down Expand Up @@ -75,10 +85,11 @@ func main() {
// http.DefaultServerMux is used for http connection, not for https
http.Handle("/", health)

agonesClient, err := getAgonesClient()
kubeClient, agonesClient, err := getClients()
if err != nil {
logger.WithError(err).Fatal("could not create agones client")
logger.WithError(err).Fatal("could not create clients")
}

// This will test the connection to agones on each readiness probe
// so if one of the allocator pod can't reach Kubernetes it will be removed
// from the Kubernetes service.
Expand All @@ -87,9 +98,7 @@ func main() {
return err
})

h := httpHandler{
agonesClient: agonesClient,
}
h := newServiceHandler(kubeClient, agonesClient, health)

// mux for https server to serve gameserver allocations
httpsMux := http.NewServeMux()
Expand Down Expand Up @@ -126,20 +135,54 @@ func main() {
logger.WithError(err).Fatal("allocation service crashed")
}

func newServiceHandler(kubeClient kubernetes.Interface, agonesClient versioned.Interface, health healthcheck.Handler) *httpHandler {
defaultResync := 30 * time.Second
agonesInformerFactory := externalversions.NewSharedInformerFactory(agonesClient, defaultResync)
kubeInformerFactory := informers.NewSharedInformerFactory(kubeClient, defaultResync)
gsCounter := gameservers.NewPerNodeCounter(kubeInformerFactory, agonesInformerFactory)

allocator := gameserverallocations.NewAllocator(
agonesInformerFactory.Multicluster().V1alpha1().GameServerAllocationPolicies(),
kubeInformerFactory.Core().V1().Secrets(),
kubeClient,
gameserverallocations.NewReadyGameServerCache(agonesInformerFactory.Agones().V1().GameServers(), agonesClient.AgonesV1(), gsCounter, health))

stop := signals.NewStopChannel()
h := httpHandler{
allocationCallback: func(gsa *allocationv1.GameServerAllocation) (k8sruntime.Object, error) {
return allocator.Allocate(gsa, stop)
},
}

kubeInformerFactory.Start(stop)
agonesInformerFactory.Start(stop)
if err := allocator.Start(stop); err != nil {
logger.WithError(err).Fatal("starting allocator failed.")
}

return &h
}

// Set up our client which we will use to call the API
func getAgonesClient() (*versioned.Clientset, error) {
func getClients() (*kubernetes.Clientset, *versioned.Clientset, error) {
// Create the in-cluster config
config, err := rest.InClusterConfig()
if err != nil {
return nil, errors.New("Could not create in cluster config")
return nil, nil, errors.New("Could not create in cluster config")
}

// Access to the Agones resources through the Agones Clientset
kubeClient, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, nil, errors.New("Could not create the kubernetes api clientset")
}

// Access to the Agones resources through the Agones Clientset
agonesClient, err := versioned.NewForConfig(config)
if err != nil {
return nil, errors.New("Could not create the agones api clientset")
return nil, nil, errors.New("Could not create the agones api clientset")
}
return agonesClient, nil
return kubeClient, agonesClient, nil
}

func getCACertPool(path string) (*x509.CertPool, error) {
Expand Down Expand Up @@ -180,7 +223,7 @@ func (h *httpHandler) postOnly(in handler) handler {
}

type httpHandler struct {
agonesClient versioned.Interface
allocationCallback func(*allocationv1.GameServerAllocation) (k8sruntime.Object, error)
}

func (h *httpHandler) allocateHandler(w http.ResponseWriter, r *http.Request) {
Expand All @@ -193,22 +236,36 @@ func (h *httpHandler) allocateHandler(w http.ResponseWriter, r *http.Request) {
logger.WithField("request", request).Infof("allocation request received")

gsa := converters.ConvertAllocationRequestV1Alpha1ToGSAV1(&request)
allocation := h.agonesClient.AllocationV1().GameServerAllocations(gsa.ObjectMeta.Namespace)
allocatedGsa, err := allocation.Create(gsa)
resultObj, err := h.allocationCallback(gsa)
if err != nil {
http.Error(w, err.Error(), httpCode(err))
logger.WithField("gsa", gsa).WithError(err).Info("calling allocation extension API failed")
logger.WithField("gsa", gsa).WithError(err).Info("allocation failed")
return
}

w.Header().Set("Content-Type", "application/json")
if status, ok := resultObj.(*metav1.Status); ok {
w.WriteHeader(int(status.Code))
err = json.NewEncoder(w).Encode(status)
if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
logger.WithError(err).Error("Unable to encode status in json")
return
}
}
allocatedGsa, ok := resultObj.(*allocationv1.GameServerAllocation)
if !ok {
http.Error(w, "internal server error", http.StatusInternalServerError)
logger.Errorf("internal server error - Bad GSA format %v", resultObj)
return
}
response := converters.ConvertGSAV1ToAllocationResponseV1Alpha1(allocatedGsa)
logger.WithField("response", response).Infof("allocation response is being sent")

w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
logger.Error(err)
logger.WithError(err).Error("Unable to encode status in json")
return
}
}
Expand Down
91 changes: 70 additions & 21 deletions cmd/allocator/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,29 @@ import (

pb "agones.dev/agones/pkg/allocation/go/v1alpha1"
allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
agonesfake "agones.dev/agones/pkg/client/clientset/versioned/fake"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
k8serror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
k8stesting "k8s.io/client-go/testing"
)

func TestAllocateHandler(t *testing.T) {
t.Parallel()

fakeAgones := &agonesfake.Clientset{}
h := httpHandler{
agonesClient: fakeAgones,
allocationCallback: func(gsa *allocationv1.GameServerAllocation) (k8sruntime.Object, error) {
return &allocationv1.GameServerAllocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Status: allocationv1.GameServerAllocationStatus{
State: allocationv1.GameServerAllocationContention,
},
}, nil
},
}

fakeAgones.AddReactor("create", "gameserverallocations", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
return true, &allocationv1.GameServerAllocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Status: allocationv1.GameServerAllocationStatus{
State: allocationv1.GameServerAllocationContention,
},
}, nil
})

request := &pb.AllocationRequest{
Namespace: "ns",
MultiClusterSetting: &pb.MultiClusterSetting{
Expand Down Expand Up @@ -78,15 +74,12 @@ func TestAllocateHandler(t *testing.T) {
func TestAllocateHandlerReturnsError(t *testing.T) {
t.Parallel()

fakeAgones := &agonesfake.Clientset{}
h := httpHandler{
agonesClient: fakeAgones,
allocationCallback: func(gsa *allocationv1.GameServerAllocation) (k8sruntime.Object, error) {
return nil, k8serror.NewBadRequest("error")
},
}

fakeAgones.AddReactor("create", "gameserverallocations", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
return true, nil, k8serror.NewBadRequest("error")
})

request := &pb.AllocationRequest{}
body, _ := json.Marshal(request)
buf := bytes.NewBuffer(body)
Expand All @@ -101,6 +94,62 @@ func TestAllocateHandlerReturnsError(t *testing.T) {
assert.Contains(t, rec.Body.String(), "error")
}

func TestHandlingStatus(t *testing.T) {
t.Parallel()

errorMessage := "GameServerAllocation is invalid"
h := httpHandler{
allocationCallback: func(gsa *allocationv1.GameServerAllocation) (k8sruntime.Object, error) {
return &metav1.Status{
Status: metav1.StatusFailure,
Message: errorMessage,
Reason: metav1.StatusReasonInvalid,
Details: &metav1.StatusDetails{
Kind: "GameServerAllocation",
Group: allocationv1.SchemeGroupVersion.Group,
},
Code: http.StatusUnprocessableEntity,
}, nil
},
}

request := &pb.AllocationRequest{}
body, _ := json.Marshal(request)
buf := bytes.NewBuffer(body)
req, err := http.NewRequest(http.MethodPost, "/", buf)
if !assert.Nil(t, err) {
return
}

rec := httptest.NewRecorder()
h.allocateHandler(rec, req)
assert.Equal(t, rec.Code, 422)
assert.Contains(t, rec.Body.String(), errorMessage)
}

func TestBadReturnType(t *testing.T) {
t.Parallel()

h := httpHandler{
allocationCallback: func(gsa *allocationv1.GameServerAllocation) (k8sruntime.Object, error) {
return &corev1.Secret{}, nil
},
}

request := &pb.AllocationRequest{}
body, _ := json.Marshal(request)
buf := bytes.NewBuffer(body)
req, err := http.NewRequest(http.MethodPost, "/", buf)
if !assert.Nil(t, err) {
return
}

rec := httptest.NewRecorder()
h.allocateHandler(rec, req)
assert.Equal(t, rec.Code, 500)
assert.Contains(t, rec.Body.String(), "internal server error")
}

func TestGettingCaCert(t *testing.T) {
t.Parallel()

Expand Down
15 changes: 15 additions & 0 deletions install/helm/agones/templates/service/allocation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,24 @@ metadata:
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["allocation.agones.dev"]
resources: ["gameserverallocations"]
verbs: ["create"]
- apiGroups: [""]
resources: ["nodes", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers", "gameserversets"]
verbs: ["get", "list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers"]
verbs: ["patch"]
- apiGroups: ["multicluster.agones.dev"]
resources: ["gameserverallocationpolicies"]
verbs: ["get", "list", "watch"]

---
# Create a ServiceAccount that will be bound to the above role
Expand Down
15 changes: 15 additions & 0 deletions install/yaml/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1174,9 +1174,24 @@ metadata:
release: agones-manual
heritage: Tiller
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["allocation.agones.dev"]
resources: ["gameserverallocations"]
verbs: ["create"]
- apiGroups: [""]
resources: ["nodes", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers", "gameserversets"]
verbs: ["get", "list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers"]
verbs: ["patch"]
- apiGroups: ["multicluster.agones.dev"]
resources: ["gameserverallocationpolicies"]
verbs: ["get", "list", "watch"]

---
# Create a ServiceAccount that will be bound to the above role
Expand Down