-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.go
181 lines (154 loc) · 5.77 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
"agones.dev/agones/pkg/client/clientset/versioned"
"agones.dev/agones/pkg/util/runtime" // for the logger
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)
// Constants which define the fleet and namespace we are using
const namespace = "default"
const fleetname = "simple-udp"
// Variables for the logger and Agones Clientset
var (
logger = runtime.NewLoggerWithSource("main")
agonesClient = getAgonesClient()
)
// A handler for the web server
type handler func(w http.ResponseWriter, r *http.Request)
// The structure of the json response
type result struct {
Status allocationv1.GameServerAllocationStatus `json:"status"`
}
// Main will set up an http server and three endpoints
func main() {
// Serve 200 status on / for k8s health checks
http.HandleFunc("/", handleRoot)
// Serve 200 status on /healthz for k8s health checks
http.HandleFunc("/healthz", handleHealthz)
// Return the GameServerStatus of the allocated replica to the authorized client
http.HandleFunc("/address", getOnly(basicAuth(handleAddress)))
// Run the HTTP server using the bound certificate and key for TLS
if err := http.ListenAndServe(":80", nil); err != nil {
logger.WithError(err).Fatal("HTTP server failed to run")
} else {
logger.Info("HTTP server is running on port 80")
}
}
// Set up our client which we will use to call the API
func getAgonesClient() *versioned.Clientset {
// Create the in-cluster config
config, err := rest.InClusterConfig()
if err != nil {
logger.WithError(err).Fatal("Could not create in cluster config")
}
// Access to the Agones resources through the Agones Clientset
agonesClient, err := versioned.NewForConfig(config)
if err != nil {
logger.WithError(err).Fatal("Could not create the agones api clientset")
} else {
logger.Info("Created the agones api clientset")
}
return agonesClient
}
// Limit verbs the web server handles
func getOnly(h handler) handler {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
h(w, r)
return
}
http.Error(w, "Get Only", http.StatusMethodNotAllowed)
}
}
// Let the web server do basic authentication
func basicAuth(pass handler) handler {
return func(w http.ResponseWriter, r *http.Request) {
key, value, _ := r.BasicAuth()
if key != "v1GameClientKey" || value != "EAEC945C371B2EC361DE399C2F11E" {
http.Error(w, "authorization failed", http.StatusUnauthorized)
return
}
pass(w, r)
}
}
// Let / return Healthy and status code 200
func handleRoot(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := io.WriteString(w, "Healthy")
if err != nil {
logger.WithError(err).Fatal("Error writing string Healthy from /")
}
}
// Let /healthz return Healthy and status code 200
func handleHealthz(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := io.WriteString(w, "Healthy")
if err != nil {
logger.WithError(err).Fatal("Error writing string Healthy from /healthz")
}
}
// Let /address return the GameServerStatus
func handleAddress(w http.ResponseWriter, r *http.Request) {
status, err := allocate()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(&result{status})
if err != nil {
logger.WithError(err).Fatal("Error writing json from /address")
}
}
// Return the number of ready game servers available to this fleet for allocation
func checkReadyReplicas() int32 {
// Get a FleetInterface for this namespace
fleetInterface := agonesClient.AgonesV1().Fleets(namespace)
// Get our fleet
fleet, err := fleetInterface.Get(fleetname, metav1.GetOptions{})
if err != nil {
logger.WithError(err).Info("Get fleet failed")
}
return fleet.Status.ReadyReplicas
}
// Move a replica from ready to allocated and return the GameServerStatus
func allocate() (allocationv1.GameServerAllocationStatus, error) {
var gsas allocationv1.GameServerAllocationStatus
// Log the values used in the allocation
logger.WithField("namespace", namespace).Info("namespace for gsa")
logger.WithField("fleetname", fleetname).Info("fleetname for gsa")
// Find out how many ready replicas the fleet has - we need at least one
readyReplicas := checkReadyReplicas()
logger.WithField("readyReplicas", readyReplicas).Info("number of ready replicas")
// Log and return an error if there are no ready replicas
if readyReplicas < 1 {
logger.WithField("fleetname", fleetname).Info("Insufficient ready replicas, cannot create fleet allocation")
gsas.State = allocationv1.GameServerAllocationUnAllocated
return gsas, errors.New("insufficient ready replicas, cannot create fleet allocation")
}
// Get a AllocationInterface for this namespace
allocationInterface := agonesClient.AllocationV1().GameServerAllocations(namespace)
// Define the allocation using the constants set earlier
gsa := &allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Required: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetname}},
}}
// Create a new allocation
gsa, err := allocationInterface.Create(gsa)
if err != nil {
// Log and return the error if the call to Create fails
logger.WithError(err).Info("Failed to create allocation")
gsas.State = allocationv1.GameServerAllocationUnAllocated
return gsas, errors.New("failed to create allocation")
}
// Log the GameServer.Staus of the new allocation, then return those values
logger.Info("New GameServer allocated: ", gsa.Status.State)
return gsa.Status, nil
}