diff --git a/build/Makefile b/build/Makefile index 0adcf78db6..dee1885635 100644 --- a/build/Makefile +++ b/build/Makefile @@ -57,7 +57,7 @@ KIND_PROFILE ?= agones KIND_CONTAINER_NAME=kind-$(KIND_PROFILE)-control-plane # Game Server image to use while doing end-to-end tests -GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.8 +GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.9 # Directory that this Makefile is in. mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) @@ -86,6 +86,7 @@ build_tag = agones-build:$(build_version) controller_tag = $(REGISTRY)/agones-controller:$(VERSION) sidecar_tag = $(REGISTRY)/agones-sdk:$(VERSION) ping_tag = $(REGISTRY)/agones-ping:$(VERSION) +allocator_tag = $(REGISTRY)/agones-allocator:$(VERSION) gomod_on = GO111MODULE=on @@ -197,7 +198,7 @@ include ./includes/sdk.mk build: build-images build-sdks # build the docker images -build-images: build-controller-image build-agones-sdk-image build-ping-image +build-images: build-controller-image build-agones-sdk-image build-ping-image build-allocator-image # package the current agones helm chart build-chart: RELEASE_VERSION ?= $(base_version) @@ -262,12 +263,13 @@ test-install-yaml: diff /tmp/agones-install/install.yaml.sorted /tmp/agones-install/install.current.yaml.sorted # Push all the images up to $(REGISTRY) -push: push-controller-image push-agones-sdk-image push-ping-image +push: push-controller-image push-agones-sdk-image push-ping-image push-allocator-image # Installs the current development version of Agones into the Kubernetes cluster install: ALWAYS_PULL_SIDECAR := true install: IMAGE_PULL_POLICY := "Always" install: PING_SERVICE_TYPE := "LoadBalancer" +install: ALLOCATOR_SERVICE_TYPE := "LoadBalancer" install: CRD_CLEANUP := true install: $(ensure-build-image) install-custom-pull-secret $(DOCKER_RUN) \ @@ -276,6 +278,7 @@ install: $(ensure-build-image) install-custom-pull-secret --set agones.image.controller.pullPolicy=$(IMAGE_PULL_POLICY),agones.image.sdk.alwaysPull=$(ALWAYS_PULL_SIDECAR) \ --set agones.image.controller.pullSecret=$(IMAGE_PULL_SECRET) \ --set agones.ping.http.serviceType=$(PING_SERVICE_TYPE),agones.ping.udp.serviceType=$(PING_SERVICE_TYPE) \ + --set agones.allocator.http.serviceType=$(ALLOCATOR_SERVICE_TYPE) \ --set agones.crds.cleanupOnDelete=$(CRD_CLEANUP) \ agones $(mount_path)/install/helm/agones/ @@ -351,6 +354,20 @@ push-ping-image: $(ensure-build-image) build-ping-image: $(ensure-build-image) build-ping-binary build-licenses build-required-src-dist docker build $(agones_path)/cmd/ping/ --tag=$(ping_tag) $(DOCKER_BUILD_ARGS) +# Build a static binary for the allocator service +build-allocator-binary: $(ensure-build-image) + $(GO_BUILD_LINUX_AMD64) \ + -tags $(GO_BUILD_TAGS) -o $(go_build_base_path)/cmd/allocator/bin/allocator \ + $(go_rebuild_flags) $(go_version_flags) -installsuffix cgo $(agones_package)/cmd/allocator + +# Pushes up the allocator image +push-allocator-image: $(ensure-build-image) + docker push $(allocator_tag) + +# Build the image for the allocator service +build-allocator-image: $(ensure-build-image) build-allocator-binary build-licenses build-required-src-dist + docker build $(agones_path)/cmd/allocator/ --tag=$(allocator_tag) $(DOCKER_BUILD_ARGS) + # push the gameservers sidecar image push-agones-sdk-image: $(ensure-build-image) docker push $(sidecar_tag) @@ -360,6 +377,7 @@ gen-install: $(ensure-build-image) docker run --rm $(common_mounts) $(DOCKER_RUN_ARGS) $(build_tag) bash -c \ 'helm template --name=agones-manual --namespace agones-system $(mount_path)/install/helm/agones \ --set agones.controller.generateTLS=false \ + --set agones.allocator.generateTLS=false \ --set agones.crds.cleanupOnDelete=false \ > $(mount_path)/install/yaml/install.yaml' diff --git a/build/build-required-src-dist.sh b/build/build-required-src-dist.sh index 30010e680b..19da8ec34c 100755 --- a/build/build-required-src-dist.sh +++ b/build/build-required-src-dist.sh @@ -26,7 +26,7 @@ tar -zcf ${TMP_DEPS_SRC} -C ${SRC_ROOT}/vendor/ \ github.com/hashicorp/golang-lru \ github.com/hashicorp/hcl -for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ; do +for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ${SRC_ROOT}/cmd/allocator/bin/ ; do mkdir -p ${ddir} cp ${TMP_DEPS_SRC} ${ddir} done diff --git a/build/extract-licenses.sh b/build/extract-licenses.sh index b776221c8d..a47a11f0b4 100755 --- a/build/extract-licenses.sh +++ b/build/extract-licenses.sh @@ -47,7 +47,7 @@ while read -r entry; do append_license ${LIBRARY} ${entry} done <<< "$(find vendor/ -regextype posix-extended -iregex '.*LICENSE(\.txt)?')" -for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ; do +for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ${SRC_ROOT}/cmd/allocator/bin/ ; do mkdir -p ${ddir} cp ${TMP_LICENSES} ${ddir} done diff --git a/build/helm.tf b/build/helm.tf index 8285d2a391..5e5891d047 100644 --- a/build/helm.tf +++ b/build/helm.tf @@ -68,6 +68,9 @@ variable "image_pull_secret" { variable "ping_service_type" { default = "LoadBalancer" } +variable "allocator_service_type" { + default = "LoadBalancer" +} variable "values_file" { default = "../install/helm/agones/values.yaml" @@ -177,6 +180,10 @@ resource "helm_release" "agones" { name = "agones.ping.udp.serviceType" value = "${var.ping_service_type}" } + set { + name = " agones.allocator.http.serviceType" + value = "${var.allocator_service_type}" + } version = "${var.agones_version}" namespace = "agones-system" } diff --git a/build/modules/aks/aks.tf b/build/modules/aks/aks.tf new file mode 100644 index 0000000000..14404b4cee --- /dev/null +++ b/build/modules/aks/aks.tf @@ -0,0 +1,106 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +provider "azuread" { + version = "=0.1.0" +} + +# Create Service Principal password +resource "azuread_service_principal_password" "aks" { + end_date = "2299-12-30T23:00:00Z" # Forever + service_principal_id = "${azuread_service_principal.aks.id}" + value = "${random_string.password.result}" +} + +# Create Azure AD Application for Service Principal +resource "azuread_application" "aks" { + name = "agones-sp" +} + +# Create Service Principal +resource "azuread_service_principal" "aks" { + application_id = "${azuread_application.aks.application_id}" +} + +# Generate random string to be used for Service Principal Password +resource "random_string" "password" { + length = 32 + special = true +} + +resource "azurerm_resource_group" "test" { + name = "agonesRG" + location = "East US" +} + +resource "azurerm_kubernetes_cluster" "test" { + name = "${var.cluster_name}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + dns_prefix = "agones" + // Version 1.11.8 has issues with RBAC on AKS + // So this parameter is commented out + //kubernetes_version = "1.11.8" + + + agent_pool_profile { + name = "default" + count = 2 + vm_size = "${var.machine_type}" + os_type = "Linux" + os_disk_size_gb = 30 + } + + service_principal { + client_id = "${azuread_application.aks.application_id}" + client_secret = "${azuread_service_principal_password.aks.value}" + } + tags = { + Environment = "Production" + } +} +resource "azurerm_network_security_group" "test" { + name = "agonesSecurityGroup" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_network_security_rule" "gameserver" { + name = "gameserver" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "UDP" + source_port_range = "*" + destination_port_range = "7000-8000" + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = "${azurerm_resource_group.test.name}" + network_security_group_name = "${azurerm_network_security_group.test.name}" +} + + +resource "azurerm_network_security_rule" "outbound" { + name = "outbound" + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = "${azurerm_resource_group.test.name}" + network_security_group_name = "${azurerm_network_security_group.test.name}" +} \ No newline at end of file diff --git a/build/modules/aks/outputs.tf b/build/modules/aks/outputs.tf new file mode 100644 index 0000000000..38c3affcbd --- /dev/null +++ b/build/modules/aks/outputs.tf @@ -0,0 +1,34 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "cluster_ca_certificate" { + value = "${base64decode(azurerm_kubernetes_cluster.test.kube_config.0.cluster_ca_certificate)}" +} + +output "client_certificate" { + value = "${azurerm_kubernetes_cluster.test.kube_config.0.client_certificate}" +} + +output "kube_config" { + value = "${azurerm_kubernetes_cluster.test.kube_config_raw}" +} + +output "host" { + value = "${azurerm_kubernetes_cluster.test.kube_config.0.host}" +} + +output "token" { + value = "${azurerm_kubernetes_cluster.test.kube_config.0.password}" +} + diff --git a/build/modules/aks/variables.tf b/build/modules/aks/variables.tf new file mode 100644 index 0000000000..854f90c14f --- /dev/null +++ b/build/modules/aks/variables.tf @@ -0,0 +1,21 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "machine_type" { + default = "Standard_D2_v2" +} + +variable "cluster_name" { + default="test-cluster" +} diff --git a/build/modules/gke/cluster.tf b/build/modules/gke/cluster.tf new file mode 100644 index 0000000000..c903cb3823 --- /dev/null +++ b/build/modules/gke/cluster.tf @@ -0,0 +1,169 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +provider "google-beta" { + version = "~> 2.4" + zone = "${lookup(var.cluster, "zone")}" +} + +/* +provider "google" { + version = "~> 2.4" +} +*/ + +data "google_client_config" "default" {} + +# echo command used for debugging purpose +# Run `terraform taint null_resource.test-setting-variables` before second execution +resource "null_resource" "test-setting-variables" { + provisioner "local-exec" { + command = "${"${format("echo Current variables set as following - name: %s, project: %s, machineType: %s, initialNodeCount: %s, zone: %s, legacyAbac: %s", + "${lookup(var.cluster, "name")}", "${lookup(var.cluster, "project")}", + "${lookup(var.cluster, "machineType")}", "${lookup(var.cluster, "initialNodeCount")}", + "${lookup(var.cluster, "zone")}", "${lookup(var.cluster, "legacyAbac")}")}"}" + } +} + + +locals { + username = "${var.password != "" ? var.username : ""}" +} + +# assert that password has correct length +# before creating the cluster to avoid +# unfinished configurations +resource "null_resource" "check-password-length" { + count = "${length(var.password) >= 16 || length(var.password) == 0 ? 0 : 1}" + "Password must be more than 16 chars in length" = true +} + +resource "google_container_cluster" "primary" { + name = "${lookup(var.cluster, "name")}" + location = "${lookup(var.cluster, "zone")}" + project = "${lookup(var.cluster, "project")}" + provider = "google-beta" + # Setting an empty username and password explicitly disables basic auth + master_auth { + username = "${local.username}" + password = "${var.password}" + } + remove_default_node_pool = true + enable_legacy_abac = "${lookup(var.cluster, "legacyAbac")}" + initial_node_count = "${lookup(var.cluster, "initialNodeCount") + 2}" +} + +resource "google_container_node_pool" "agones-gameserver" { + name = "default" + cluster = "${google_container_cluster.primary.name}" + location = "${google_container_cluster.primary.location}" + project = "${lookup(var.cluster, "project")}" + provider = "google-beta" + node_count = "${lookup(var.cluster, "initialNodeCount")}" + node_config = { + machine_type = "${lookup(var.cluster, "machineType")}" + oauth_scopes = [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring", + "https://www.googleapis.com/auth/service.management.readonly", + "https://www.googleapis.com/auth/servicecontrol", + "https://www.googleapis.com/auth/trace.append", + ] + + tags = ["game-server"] + timeouts = { + create = "30m" + update = "40m" + } + } +} + +resource "google_container_node_pool" "agones-system" { + name = "agones-system" + cluster = "${google_container_cluster.primary.name}" + location = "${google_container_cluster.primary.location}" + project = "${lookup(var.cluster, "project")}" + provider = "google-beta" + node_count = 1 + node_config = { + preemptible = true + machine_type = "n1-standard-4" + + oauth_scopes = [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring", + "https://www.googleapis.com/auth/service.management.readonly", + "https://www.googleapis.com/auth/servicecontrol", + "https://www.googleapis.com/auth/trace.append", + ] + labels = { + "stable.agones.dev/agones-system" = "true" + } + taint = { + key = "stable.agones.dev/agones-system" + value = "true" + effect = "NO_EXECUTE" + } + } +} + +resource "google_container_node_pool" "agones-metrics" { + name = "agones-metrics" + cluster = "${google_container_cluster.primary.name}" + location = "${google_container_cluster.primary.location}" + project = "${lookup(var.cluster, "project")}" + provider = "google-beta" + node_count = 1 + node_config = { + preemptible = true + machine_type = "n1-standard-4" + + oauth_scopes = [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring", + "https://www.googleapis.com/auth/service.management.readonly", + "https://www.googleapis.com/auth/servicecontrol", + "https://www.googleapis.com/auth/trace.append", + ] + labels = { + "stable.agones.dev/agones-metrics" = "true" + } + taint = { + key = "stable.agones.dev/agones-metrics" + value = "true" + effect = "NO_EXECUTE" + } + } +} + +resource "google_compute_firewall" "default" { + name = "game-server-firewall-firewall-${lookup(var.cluster, "name")}" + project = "${lookup(var.cluster, "project")}" + network = "${google_compute_network.default.name}" + + allow { + protocol = "udp" + ports = ["${var.ports}"] + } + + source_tags = ["game-server"] +} + +resource "google_compute_network" "default" { + project = "${lookup(var.cluster, "project")}" + name = "agones-network-${lookup(var.cluster, "name")}" +} \ No newline at end of file diff --git a/build/modules/gke/outputs.tf b/build/modules/gke/outputs.tf new file mode 100644 index 0000000000..ce1e098d88 --- /dev/null +++ b/build/modules/gke/outputs.tf @@ -0,0 +1,35 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The following outputs allow authentication and connectivity to the GKE Cluster +# by using certificate-based authentication. +output "client_certificate" { + value = "${google_container_cluster.primary.master_auth.0.client_certificate}" +} + +output "client_key" { + value = "${google_container_cluster.primary.master_auth.0.client_key}" +} + +output "cluster_ca_certificate" { + value = "${base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate)}" +} + +output "host" { + value = "https://${google_container_cluster.primary.endpoint}" +} + +output "token" { + value = "${data.google_client_config.default.access_token}" +} diff --git a/build/modules/gke/variables.tf b/build/modules/gke/variables.tf new file mode 100644 index 0000000000..3d90aaeb8f --- /dev/null +++ b/build/modules/gke/variables.tf @@ -0,0 +1,38 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Password for the Kubernetes API. +# Could be defined using GKE_PASSWORD env variable +# or by setting `password="somepass"` string in build/terraform.tfvars +variable "password" {default = ""} +variable "username" {default = "admin"} + +# Ports can be overriden using tfvars file +variable "ports" {default="7000-8000"} + +# Set of GKE cluster parameters which defines its name, zone +# and primary node pool configuration. +# It is crucial to set valid ProjectID for "project". +variable "cluster" { + description = "Set of GKE cluster parameters." + type = "map" + default = { + "zone" = "us-west1-c" + "name" = "test-cluster" + "machineType" = "n1-standard-4" + "initialNodeCount" = "4" + "legacyAbac" = false + "project" = "agones" + } +} diff --git a/build/modules/helm/helm.tf b/build/modules/helm/helm.tf new file mode 100644 index 0000000000..21980343bd --- /dev/null +++ b/build/modules/helm/helm.tf @@ -0,0 +1,165 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "kubernetes_service_account" "tiller" { + metadata { + name = "tiller" + namespace = "kube-system" + } + automount_service_account_token = true +} + +resource "kubernetes_cluster_role_binding" "tiller" { + metadata { + name = "tiller" + } + + role_ref { + kind = "ClusterRole" + name = "cluster-admin" + api_group = "rbac.authorization.k8s.io" + } + + subject { + kind = "ServiceAccount" + name = "tiller" + + api_group = "" + namespace = "kube-system" + } + + depends_on = ["kubernetes_service_account.tiller"] +} + +provider "kubernetes" { + version = "~> 1.5" + load_config_file = false + host = "${var.host}" + token = "${var.token}" + cluster_ca_certificate = "${var.cluster_ca_certificate}" +} + +provider "helm" { + version = "~> 0.7" + + debug = true + install_tiller = true + service_account = "${kubernetes_service_account.tiller.metadata.0.name}" + tiller_image = "gcr.io/kubernetes-helm/tiller:v2.12.3" + + kubernetes { + load_config_file = false + host = "${var.host}" + token = "${var.token}" + cluster_ca_certificate = "${var.cluster_ca_certificate}" + } +} + +# In Terraform version 0.12 Interpolation would only evaluate one branch of a condition +# https://github.com/hashicorp/terraform/issues/15605 +# so we can remove this and change values in helm_release to: +# +# values = [ +# "${length(var.values_file) == 0 ? "" : file("${var.values_file}"))}" +# ] +data "null_data_source" "values_file" { + count = "${length(var.values_file) == 0 ? 0 : 1}" + inputs = { + "values" = "${file("${var.values_file}")}" + } +} + + +data "helm_repository" "agones" { + name = "agones" + url = "https://agones.dev/chart/stable" + + depends_on = ["kubernetes_cluster_role_binding.tiller"] +} + + +# TODO: remove - not needed in Terraform 0.12 +locals { + values = { + params = "${join("", data.null_data_source.values_file.*.outputs.values)}" + } + # Skip image tag if it is not needed + # for installing latest image it would use chart value + tag_name = "${var.agones_version != "" ? "agones.image.tag" : "skip"}" +} + +resource "helm_release" "agones" { + name = "agones" + force_update = "true" + repository = "${data.helm_repository.agones.metadata.0.name}" + chart = "${var.chart}" + timeout = 420 + + values = [ + # Switch in terraform 0.12 to: + # "${length(var.values_file) == 0 ? "" : file("${var.values_file}"))}" + "${length(var.values_file) == 0 ? "" : local.values["params"]}" + ] + + set { + name = "crds.CleanupOnDelete" + value = "${var.crd_cleanup}" + } + set { + name = "${local.tag_name}" + value = "${var.agones_version}" + } + set { + name = "agones.image.registry" + value = "${var.image_registry}" + } + set { + name = "agones.image.controller.pullPolicy" + value = "${var.pull_policy}" + } + set { + name = "agones.image.sdk.alwaysPull" + value = "${var.always_pull_sidecar}" + } + set { + name = "agones.image.controller.pullSecret" + value = "${var.image_pull_secret}" + } + set { + name = " agones.ping.http.serviceType" + value = "${var.ping_service_type}" + } + set { + name = "agones.ping.udp.serviceType" + value = "${var.ping_service_type}" + } + version = "${var.agones_version}" + namespace = "agones-system" + + depends_on = ["null_resource.helm_init", "kubernetes_cluster_role_binding.tiller"] +} + +provider "null" { + version = "~> 2.1" +} + +# Creates folder with repositories so that helm provider would not fail +resource "null_resource" "helm_init" { + triggers = { + always_run = "${timestamp()}" + } + provisioner "local-exec" { + command = "helm init --client-only" + } +} diff --git a/build/modules/helm/variables.tf b/build/modules/helm/variables.tf new file mode 100644 index 0000000000..4f28fc38ee --- /dev/null +++ b/build/modules/helm/variables.tf @@ -0,0 +1,56 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Helm variables + +variable "chart" { + default = "../../../install/helm/agones/" +} + +variable "agones_version" { + default = "" +} + +variable "host" {} + +variable "token" {} + +variable "cluster_ca_certificate" {} +/* + host = "${azurerm_kubernetes_cluster.test.kube_config.0.host}" + token = "${azurerm_kubernetes_cluster.test.kube_config.0.password}" + cluster_ca_certificate = "${base64decode(azurerm_kubernetes_cluster.test.kube_config.0.cluster_ca_certificate)}" +*/ +variable "crd_cleanup" { + default = "true" +} +variable "image_registry" { + default = "gcr.io/agones-images" +} +variable "pull_policy" { + default = "Always" +} +variable "always_pull_sidecar" { + default = "true" +} +variable "image_pull_secret" { + default = "" +} +variable "ping_service_type" { + default = "LoadBalancer" +} + +variable "values_file" { + default = "../../../install/helm/agones/values.yaml" +} \ No newline at end of file diff --git a/cmd/allocator/Dockerfile b/cmd/allocator/Dockerfile new file mode 100644 index 0000000000..d85520746b --- /dev/null +++ b/cmd/allocator/Dockerfile @@ -0,0 +1,24 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM alpine:3.8 + +RUN apk --update add ca-certificates && \ + adduser -D agones + +COPY --chown=agones:root ./bin/allocator /home/agones/allocator +COPY --chown=agones:root ./bin/LICENSES ./bin/dependencies-src.tgz /home/agones/ + +USER agones +ENTRYPOINT ["/home/agones/allocator"] diff --git a/cmd/allocator/main.go b/cmd/allocator/main.go new file mode 100644 index 0000000000..2001407032 --- /dev/null +++ b/cmd/allocator/main.go @@ -0,0 +1,169 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + + allocationv1alpha1 "agones.dev/agones/pkg/apis/allocation/v1alpha1" + "agones.dev/agones/pkg/client/clientset/versioned" + "agones.dev/agones/pkg/util/runtime" + k8serror "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/rest" +) + +var ( + logger = runtime.NewLoggerWithSource("main") +) + +const ( + certDir = "/home/allocator/client-ca/" + tlsDir = "/home/allocator/tls/" + port = "8443" +) + +// A handler for the web server +type handler func(w http.ResponseWriter, r *http.Request) + +func main() { + agonesClient, err := getAgonesClient() + if err != nil { + logger.WithError(err).Fatal("could not create agones client") + } + + h := httpHandler{ + agonesClient: agonesClient, + namespace: os.Getenv("NAMESPACE"), + } + + // TODO: add liveness probe + http.HandleFunc("/v1alpha1/gameserverallocation", h.postOnly(h.allocateHandler)) + + caCertPool, err := getCACertPool(certDir) + if err != nil { + logger.WithError(err).Fatal("could not get CA certs") + } + + cfg := &tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: caCertPool, + } + srv := &http.Server{ + Addr: ":" + port, + TLSConfig: cfg, + } + + err = srv.ListenAndServeTLS(tlsDir+"tls.crt", tlsDir+"tls.key") + logger.WithError(err).Fatal("allocation service crashed") +} + +// Set up our client which we will use to call the API +func getAgonesClient() (*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") + } + + // 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 agonesClient, nil +} + +func getCACertPool(path string) (*x509.CertPool, error) { + // Add all certificates under client-certs path because there could be multiple clusters + // and all client certs should be added. + caCertPool := x509.NewCertPool() + filesInfo, err := ioutil.ReadDir(path) + if err != nil { + return nil, fmt.Errorf("error reading certs from dir %s: %s", path, err.Error()) + } + + for _, file := range filesInfo { + if strings.HasSuffix(file.Name(), ".crt") || strings.HasSuffix(file.Name(), ".pem") { + certFile := filepath.Join(path, file.Name()) + caCert, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, fmt.Errorf("ca cert is not readable or missing: %s", err.Error()) + } + if !caCertPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("client cert %s cannot be installed", certFile) + } + logger.Infof("client cert %s is installed", certFile) + } + } + + return caCertPool, nil +} + +// Limit verbs the web server handles +func (h *httpHandler) postOnly(in handler) handler { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + in(w, r) + return + } + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } +} + +type httpHandler struct { + agonesClient versioned.Interface + namespace string +} + +func (h *httpHandler) allocateHandler(w http.ResponseWriter, r *http.Request) { + gsa := allocationv1alpha1.GameServerAllocation{} + if err := json.NewDecoder(r.Body).Decode(&gsa); err != nil { + http.Error(w, "invalid request", http.StatusBadRequest) + return + } + + allocation := h.agonesClient.AllocationV1alpha1().GameServerAllocations(h.namespace) + allocatedGsa, err := allocation.Create(&gsa) + if err != nil { + http.Error(w, err.Error(), httpCode(err)) + logger.Debug(err) + return + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(allocatedGsa) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + logger.Error(err) + return + } +} + +func httpCode(err error) int { + code := http.StatusInternalServerError + switch t := err.(type) { + case k8serror.APIStatus: + code = int(t.Status().Code) + } + return code +} diff --git a/cmd/allocator/main_test.go b/cmd/allocator/main_test.go new file mode 100644 index 0000000000..6bd6717550 --- /dev/null +++ b/cmd/allocator/main_test.go @@ -0,0 +1,134 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "testing" + + "agones.dev/agones/pkg/apis/allocation/v1alpha1" + agonesfake "agones.dev/agones/pkg/client/clientset/versioned/fake" + "github.com/stretchr/testify/assert" + k8serror "k8s.io/apimachinery/pkg/api/errors" + 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, + namespace: "default", + } + + fakeAgones.AddReactor("create", "gameserverallocations", func(action k8stesting.Action) (bool, k8sruntime.Object, error) { + return true, &v1alpha1.GameServerAllocation{ + Status: v1alpha1.GameServerAllocationStatus{ + State: v1alpha1.GameServerAllocationContention, + }, + }, nil + }) + + gsa := &v1alpha1.GameServerAllocation{} + body, _ := json.Marshal(gsa) + buf := bytes.NewBuffer(body) + req, err := http.NewRequest(http.MethodPost, "/", buf) + if !assert.Nil(t, err) { + return + } + + rec := httptest.NewRecorder() + h.allocateHandler(rec, req) + + ret := &v1alpha1.GameServerAllocation{} + assert.Equal(t, rec.Code, 200) + assert.Equal(t, "application/json", rec.Header()["Content-Type"][0]) + err = json.Unmarshal(rec.Body.Bytes(), ret) + assert.NoError(t, err) + assert.Equal(t, v1alpha1.GameServerAllocationContention, ret.Status.State) +} + +func TestAllocateHandlerReturnsError(t *testing.T) { + t.Parallel() + + fakeAgones := &agonesfake.Clientset{} + h := httpHandler{ + agonesClient: fakeAgones, + namespace: "default", + } + + fakeAgones.AddReactor("create", "gameserverallocations", func(action k8stesting.Action) (bool, k8sruntime.Object, error) { + return true, nil, k8serror.NewBadRequest("error") + }) + + gsa := &v1alpha1.GameServerAllocation{} + body, _ := json.Marshal(gsa) + 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, 400) + assert.Contains(t, rec.Body.String(), "error") +} + +func TestGettingCaCert(t *testing.T) { + t.Parallel() + + file, err := ioutil.TempFile(".", "*.crt") + if assert.Nil(t, err) { + defer os.Remove(file.Name()) // nolint: errcheck + _, err = file.WriteString(clientCert) + if assert.Nil(t, err) { + certPool, err := getCACertPool("./") + if assert.Nil(t, err) { + assert.Len(t, certPool.Subjects(), 1) + } + } + } +} + +var clientCert = `-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIUduDWtqpUsp3rZhCEfUrzI05laVIwDQYJKoZIhvcNAQEL +BQAwbTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u +ZG9uMRgwFgYDVQQKDA9HbG9iYWwgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFy +dG1lbnQxCjAIBgNVBAMMASowHhcNMTkwNTAyMjIzMDQ3WhcNMjkwNDI5MjIzMDQ3 +WjBtMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25k +b24xGDAWBgNVBAoMD0dsb2JhbCBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0 +bWVudDEKMAgGA1UEAwwBKjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKGDasjadVwe0bXUEQfZCkMEAkzn0qTud3RYytympmaS0c01SWFNZwPRO0rpdIOZ +fyXVXVOAhgmgCR6QuXySmyQIoYl/D6tVhc5r9FyWPIBtzQKCJTX0mZOZwMn22qvo +bfnDnVsZ1Ny3RLZIF3um3xovvePXyg1z7D/NvCogNuYpyUUEITPZX6ss5ods/U78 +BxLhKrT8iyu61ZC+ZegbHQqFRngbeb348gE1JwKTslDfe4oH7tZ+bNDZxnGcvh9j +eyagpM0zys4gFfQf/vfD2aEsUJ+GesUQC6uGVoGnTFshFhBsAK6vpIQ4ZQujaJ0r +NKgJ/ccBJFiJXMCR44yWFY0CAwEAAaNTMFEwHQYDVR0OBBYEFEe1gDd8JpzgnvOo +1AEloAXxmxHCMB8GA1UdIwQYMBaAFEe1gDd8JpzgnvOo1AEloAXxmxHCMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI5GyuakVgunerCGCSN7Ghsr +ys9vJytbyT+BLmxNBPSXWQwcm3g9yCDdgf0Y3q3Eef7IEZu4I428318iLzhfKln1 +ua4fxvmTFKJ65lQKNkc6Y4e3w1t+C2HOl6fOIVT231qsCoM5SAwQQpqAzEUj6kZl +x+3avw9KSlXqR/mCAkePyoKvprxeb6RVDdq92Ug0qzoAHLpvIkuHdlF0dNp6/kO0 +1pVL0BqW+6UTimSSvH8F/cMeYKbkhpE1u2c/NtNwsR2jN4M9kl3KHqkynk67PfZv +pwlCqZx4M8FpdfCbOZeRLzClUBdD5qzev0L3RNUx7UJzEIN+4LCBv37DIojNOyA= +-----END CERTIFICATE----- +` diff --git a/examples/fleet.yaml b/examples/fleet.yaml index eea0dc334a..9a2c508c8d 100644 --- a/examples/fleet.yaml +++ b/examples/fleet.yaml @@ -67,4 +67,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.9 \ No newline at end of file diff --git a/examples/nodejs-simple/src/index.js b/examples/nodejs-simple/src/index.js index 13334e0959..abeb92feed 100644 --- a/examples/nodejs-simple/src/index.js +++ b/examples/nodejs-simple/src/index.js @@ -1,8 +1,8 @@ const AgonesSDK = require('agones'); -let agonesSDK = new AgonesSDK(); +const agonesSDK = new AgonesSDK(); -let connect = async function() { +const connect = async function() { agonesSDK.watchGameServer((result) => { console.log('watch', result); }); @@ -11,7 +11,7 @@ let connect = async function() { await agonesSDK.ready(); await agonesSDK.setLabel("label", "labelValue"); await agonesSDK.setAnnotation("annotation", "annotationValue"); - let result = await agonesSDK.getGameServer(); + const result = await agonesSDK.getGameServer(); console.log('gameServer', result); setTimeout(() => { console.log('send health ping'); @@ -22,7 +22,7 @@ let connect = async function() { agonesSDK.shutdown(); }, 4000); setTimeout(() => { - process.exit(); + agonesSDK.close(); }, 6000); } catch (error) { console.error(error); diff --git a/examples/simple-udp/Makefile b/examples/simple-udp/Makefile index 7a1030e08e..411710c370 100644 --- a/examples/simple-udp/Makefile +++ b/examples/simple-udp/Makefile @@ -27,7 +27,7 @@ REPOSITORY = gcr.io/agones-images mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) project_path := $(dir $(mkfile_path)) -server_tag = $(REPOSITORY)/udp-server:0.8 +server_tag = $(REPOSITORY)/udp-server:0.9 root_path = $(realpath $(project_path)/../..) # _____ _ diff --git a/examples/simple-udp/fleet-distributed.yaml b/examples/simple-udp/fleet-distributed.yaml index 4a397e5baa..ff53f58689 100644 --- a/examples/simple-udp/fleet-distributed.yaml +++ b/examples/simple-udp/fleet-distributed.yaml @@ -32,7 +32,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "32Mi" diff --git a/examples/simple-udp/fleet.yaml b/examples/simple-udp/fleet.yaml index c0448327bb..e2d91c8de4 100644 --- a/examples/simple-udp/fleet.yaml +++ b/examples/simple-udp/fleet.yaml @@ -27,7 +27,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "64Mi" diff --git a/examples/simple-udp/gameserver.yaml b/examples/simple-udp/gameserver.yaml index 121551ff68..306f0ec902 100644 --- a/examples/simple-udp/gameserver.yaml +++ b/examples/simple-udp/gameserver.yaml @@ -25,7 +25,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "32Mi" diff --git a/examples/simple-udp/gameserverset.yaml b/examples/simple-udp/gameserverset.yaml index e62b2da8cb..6d534e3c18 100644 --- a/examples/simple-udp/gameserverset.yaml +++ b/examples/simple-udp/gameserverset.yaml @@ -31,4 +31,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.9 \ No newline at end of file diff --git a/examples/simple-udp/main.go b/examples/simple-udp/main.go index bedd977d8e..d2c6b87665 100644 --- a/examples/simple-udp/main.go +++ b/examples/simple-udp/main.go @@ -85,6 +85,8 @@ func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) { switch parts[0] { // shuts down the gameserver case "EXIT": + // respond here, as we os.Exit() before we get to below + respond(conn, sender, "ACK: "+txt+"\n") exit(s) // turns off the health pings diff --git a/examples/terraform-submodules/aks/module.tf b/examples/terraform-submodules/aks/module.tf new file mode 100644 index 0000000000..1812fc74ea --- /dev/null +++ b/examples/terraform-submodules/aks/module.tf @@ -0,0 +1,55 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Run: +// terraform apply [-var agones_version="0.9.0"] + +// Install latest version of agones +variable "agones_version" { + default="" +} +variable "cluster_name" { + default="test-cluster" +} + +variable "machine_type" {default = "Standard_D2_v2"} + +module "aks_cluster" { + source = "git::https://github.com/GoogleCloudPlatform/agones.git//build/modules/aks/?ref=master" + + machine_type = "${var.machine_type}" + cluster_name = "${var.cluster_name}" +} + +module "helm_agones" { + source = "git::https://github.com/GoogleCloudPlatform/agones.git//build/modules/helm/?ref=master" + + agones_version = "${var.agones_version}" + values_file="" + chart="agones" + host="${module.aks_cluster.host}" + token="${module.aks_cluster.token}" + cluster_ca_certificate="${module.aks_cluster.cluster_ca_certificate}" +} + +output "host" { + value = "${module.aks_cluster.host}" +} +output "token" { + value = "${module.aks_cluster.token}" +} +output "cluster_ca_certificate" { + value = "${module.aks_cluster.cluster_ca_certificate}" +} diff --git a/examples/terraform-submodules/gke-local/main.tf b/examples/terraform-submodules/gke-local/main.tf new file mode 100644 index 0000000000..6c9a1fd0ca --- /dev/null +++ b/examples/terraform-submodules/gke-local/main.tf @@ -0,0 +1,62 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Run: +// terraform apply [-var agones_version="0.9.0"] + +// Install latest version of agones +variable "agones_version" { + default="" +} + +// Your GKE project name +variable "project" { + default="agones" +} +module "gke_cluster" { + + source = "../../../build/modules/gke" + + cluster = { + "project" = "${var.project}" + "zone" = "us-west1-c" + "name" = "test-cluster" + "machineType" = "n1-standard-4" + "initialNodeCount" = "4" + "legacyAbac" = false + } +} + +module "helm_agones" { + + source = "../../../build/modules/helm" + + agones_version = "${var.agones_version}" + values_file="" + chart="agones" + host="${module.gke_cluster.host}" + token="${module.gke_cluster.token}" + cluster_ca_certificate="${module.gke_cluster.cluster_ca_certificate}" +} + +output "host" { + value = "${module.gke_cluster.host}" +} +output "token" { + value = "${module.gke_cluster.token}" +} +output "cluster_ca_certificate" { + value = "${module.gke_cluster.cluster_ca_certificate}" +} diff --git a/examples/terraform-submodule/module.tf b/examples/terraform-submodules/gke/module.tf similarity index 100% rename from examples/terraform-submodule/module.tf rename to examples/terraform-submodules/gke/module.tf diff --git a/install/helm/agones/certs/allocator/client-ca/ca.crt b/install/helm/agones/certs/allocator/client-ca/ca.crt new file mode 100644 index 0000000000..158b88a4ad --- /dev/null +++ b/install/helm/agones/certs/allocator/client-ca/ca.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyzCCArOgAwIBAgIUKbZWR0Ji6BTqkdbB90Om4uJHi38wDQYJKoZIhvcNAQEL +BQAwdTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEuMCwGCSqGSIb3DQEJARYfYWdvbmVz +LWRpc2N1c3NAZ29vZ2xlZ3JvdXBzLmNvbTAeFw0xOTA1MTQxODMxNTlaFw0yOTA1 +MTExODMxNTlaMHUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxLjAsBgkqhkiG9w0BCQEW +H2Fnb25lcy1kaXNjdXNzQGdvb2dsZWdyb3Vwcy5jb20wggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDetyULwNujFbXZtGFcOCAwTYfU54D4Gwdk1t7RWqa5 +//riKswqkcrw68lNkINm+yMNgFkdt2zglwjCgjJZXcFovP/7/g32CN5W6gyx7cB0 +FTXJVWe3uUG6PW5YPaH12CucHCq5iNzwlEyDUPCJAaPO5fUBlbCRULYPhBnJXZXr +B6zdtc6R1uykXNfVhZYHvIVr5oWIFygCNnjvMgHmkvfLNximSgpsKXyLjW11SWwx +mxppowklwK2QqaxnaSOTYv7ucmL0O8XE89n/So1YnIOgdQyNmNaUJ4D6MhxvKeJA +Tfg6k91QwW9+EIiht+NjCMspx4n6yliRno1GMHsZIjbHAgMBAAGjUzBRMB0GA1Ud +DgQWBBQxjrrtKpNpM6MH9868q65dq4770TAfBgNVHSMEGDAWgBQxjrrtKpNpM6MH +9868q65dq4770TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCo +cFsxscFNj8wTkuhvmEG285zCs98QjGqsOTB4/GkrTmbNQoqObnc6iNC4bs9AC6HO +abNRC6m5UaVQVH3UB6O3+Fm40TNoTZt1GBF3/LtdwgdRkmFThiB/fIttT0PGaEfe +A+yeyhNbVyUgOYI7nK5PK5s7gBSykGLzXIielommrA6K29/ZNlj+hNwKWHIBZ/Cn +cOkVgPBkeBkY+VrVc3n/HBclj+s7R75KrhpNKOMfv3jorhRAQ8eQoIhHbiqcbM7q +lzpl39Uxsj2V1YBayu9inmqJiBf4SMuntzUpPzKal0O+ieKA2bKSSTlGGnr3RRvy +MXdg7ZXZewKqbbS/BrHq +-----END CERTIFICATE----- diff --git a/install/helm/agones/certs/allocator/server.crt b/install/helm/agones/certs/allocator/server.crt new file mode 100644 index 0000000000..158b88a4ad --- /dev/null +++ b/install/helm/agones/certs/allocator/server.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyzCCArOgAwIBAgIUKbZWR0Ji6BTqkdbB90Om4uJHi38wDQYJKoZIhvcNAQEL +BQAwdTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEuMCwGCSqGSIb3DQEJARYfYWdvbmVz +LWRpc2N1c3NAZ29vZ2xlZ3JvdXBzLmNvbTAeFw0xOTA1MTQxODMxNTlaFw0yOTA1 +MTExODMxNTlaMHUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxLjAsBgkqhkiG9w0BCQEW +H2Fnb25lcy1kaXNjdXNzQGdvb2dsZWdyb3Vwcy5jb20wggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDetyULwNujFbXZtGFcOCAwTYfU54D4Gwdk1t7RWqa5 +//riKswqkcrw68lNkINm+yMNgFkdt2zglwjCgjJZXcFovP/7/g32CN5W6gyx7cB0 +FTXJVWe3uUG6PW5YPaH12CucHCq5iNzwlEyDUPCJAaPO5fUBlbCRULYPhBnJXZXr +B6zdtc6R1uykXNfVhZYHvIVr5oWIFygCNnjvMgHmkvfLNximSgpsKXyLjW11SWwx +mxppowklwK2QqaxnaSOTYv7ucmL0O8XE89n/So1YnIOgdQyNmNaUJ4D6MhxvKeJA +Tfg6k91QwW9+EIiht+NjCMspx4n6yliRno1GMHsZIjbHAgMBAAGjUzBRMB0GA1Ud +DgQWBBQxjrrtKpNpM6MH9868q65dq4770TAfBgNVHSMEGDAWgBQxjrrtKpNpM6MH +9868q65dq4770TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCo +cFsxscFNj8wTkuhvmEG285zCs98QjGqsOTB4/GkrTmbNQoqObnc6iNC4bs9AC6HO +abNRC6m5UaVQVH3UB6O3+Fm40TNoTZt1GBF3/LtdwgdRkmFThiB/fIttT0PGaEfe +A+yeyhNbVyUgOYI7nK5PK5s7gBSykGLzXIielommrA6K29/ZNlj+hNwKWHIBZ/Cn +cOkVgPBkeBkY+VrVc3n/HBclj+s7R75KrhpNKOMfv3jorhRAQ8eQoIhHbiqcbM7q +lzpl39Uxsj2V1YBayu9inmqJiBf4SMuntzUpPzKal0O+ieKA2bKSSTlGGnr3RRvy +MXdg7ZXZewKqbbS/BrHq +-----END CERTIFICATE----- diff --git a/install/helm/agones/certs/allocator/server.key b/install/helm/agones/certs/allocator/server.key new file mode 100644 index 0000000000..074d228fc8 --- /dev/null +++ b/install/helm/agones/certs/allocator/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwvzpNSlVvochgfFuj96OdIgMyrvQarrhU/JPMrP7Gz71OU7n +wxh+11jluFKeQLPMROmQ7ANzMCuOk4oJiAfXFRlyUxix/bn0JGlekcfBRSizTUUp +wn/hZhUatdzM1nHnbbhezyDtxeAtWQroEWCpSGU6b2FFxn3O+r1nc4QtmuE1C+KD +w5qUyZtxshDS97v3x8zu92BIjCFITy8Nr1t4Xc9fmgzFXRta5gsIppXcNHPERo3/ +3ZSpVx5q0HOgj9Ha33MlD5Y4cRumro2nQP6sm+72/XwL0/N5sUT9idR/eAN7dZln +pfj/9dpzYDRxPOHespl19tPvtOX2Pcwr3DgFZwIDAQABAoIBAEozCd+UAjAFpieX +ozYWP+lyWEPRoqbqxJI4VBD8UmL020Zak3E3YhU6m/g/YD9I7EORCff1/CBpgtD8 +J1JfgWZjD5E1hY4C9Dfi7PzWoeJacZjG5/Q+y5wYDdkE0+IK6EFldpaldjXlb/xB +XTw3p1OISetWfcHh4CCkcLiRpPUZcY3Y6Ji0WDklabkaaFGn2GBY6atUHAqSRX5/ +CvxNa9GLUSJCXwQLNES1jVocjCfbq38fZtr9YzTF6VSbpy0c7Rp4DPmRXOlEDsTq +0xvalvVl5o4uHFLhgJXKvFt5LW1e6crSLzMGCy+EQy0oXvyt82iCLMzLYdg1Urd8 +pk3dFIkCgYEA8dxbYm5Ds3ZczxP8arjhUEOuIhE95BZjpXMVTeXCUs8WhyargDMB +ns5p6RXyZ4YiySez7ay6thwT/JNkt5cstqpW1ccQh/wiQcZgVrBOvgk16TUaDo3r +illSmFo5q61xJqaz6IVkQbWEuC1jyDeRUyMJONKNGAUhRugzlKoxN80CgYEAzmMQ +W+uB4ZZHdun1JVO4pVvD9F2XSXdUIaAOCCRrT+FyWTWkCp5e1fir/kyndNdL0wHA +YLwlQ0P6+wyQ+wzoQIjxkl6Ji0U99t2rditakdleTRDgwevQM4K9ERpR4olasWwT +Oid/RtzxsPW+IV4eQXvG1eAqkOctY58PKg/71gMCgYEAuA+d0Evf4+y49rLcDxAC +EIZhN/XtJu7PCLOLTAwFleWZ3GLWc6rdmdC2kzcrkhNn17QKX19rfHnjz/0P0hQP +aDg7+1cSQprkKzBgUri8RC1YRNEvslHZJiGSFPXdG8TT7rlBAzy4rrUIeu24GUAI +rb9lHkRGMjh9OawuzP20lSUCgYA4eIqlXA8xtnjfZKybBglj1XQMjP80Lt5qRO0R +9UUXmkw7dJ3p+eNhEKWe80JuiepJVhVZYqGfgh5OSuXwi1uRLOdMcWmZHd0Sixy7 +PKmPdraCx7d32BojvgYVg+ob3hFesn+gTeZZcWMAVH0tjB8jM8b4BlofQ+H7fC4e +CDmUGQKBgEFxGn8yV5tx6egR2N9lwhywoC8qUrQa5KeE6cAU1k4cprMqQEriRwIR +Gf80q25aAnhFvE4N+7qNEirC+kiaw3+DCCmsQ+rnlc6Trs560fHsqISBTj/i9X0r +md2bhpbaz8YuMVm0pb9/c4SZivBz64yK2Ob4snFW0fLkLQQ+pjK5 +-----END RSA PRIVATE KEY----- diff --git a/install/helm/agones/templates/NOTES.txt b/install/helm/agones/templates/NOTES.txt index 9d31364ee5..7a534bcdb3 100644 --- a/install/helm/agones/templates/NOTES.txt +++ b/install/helm/agones/templates/NOTES.txt @@ -19,7 +19,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 Finally don't forget to explore our documentation and usage guides on how to develop and host dedicated game servers on top of Agones. : diff --git a/install/helm/agones/templates/service/allocation.yaml b/install/helm/agones/templates/service/allocation.yaml new file mode 100644 index 0000000000..cae5cb96ef --- /dev/null +++ b/install/helm/agones/templates/service/allocation.yaml @@ -0,0 +1,203 @@ +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.agones.allocator.install }} +# Define a Service for the gameserver-allocator +apiVersion: v1 +kind: Service +metadata: + name: gameserver-allocator + namespace: {{ .Release.Namespace }} + labels: + component: allocator + app: {{ template "agones.name" . }} + chart: {{ template "agones.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + selector: + multicluster.agones.dev/role: allocator + ports: + - port: {{ .Values.agones.allocator.http.port }} + name: https + targetPort: 8443 + protocol: TCP + type: {{ .Values.agones.allocator.http.serviceType }} + +--- +# Deploy a pod to run the gameserver-allocator code +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gameserver-allocator + namespace: {{ .Release.Namespace }} + labels: + multicluster.agones.dev/role: allocator + app: {{ template "agones.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.agones.allocator.replicas }} + selector: + matchLabels: + multicluster.agones.dev/role: allocator + app: {{ template "agones.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + template: + metadata: + labels: + multicluster.agones.dev/role: allocator + app: {{ template "agones.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + spec: + serviceAccountName: gameserver-allocator + volumes: + - name: tls + secret: + secretName: allocator-tls + - name: client-ca + secret: + secretName: allocator-client-ca + containers: + - name: agones-allocator + image: "{{ .Values.agones.image.registry }}/{{ .Values.agones.image.allocator.name}}:{{ default .Values.agones.image.tag .Values.agones.image.allocator.tag }}" + imagePullPolicy: {{ .Values.agones.image.controller.pullPolicy }} + ports: + - name: https + containerPort: 8443 + volumeMounts: + - mountPath: /home/allocator/tls + name: tls + readOnly: true + - mountPath: /home/allocator/client-ca + name: client-ca + readOnly: true + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + +--- +# Create a Role in the default namespace that grants access to the agones api +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gameserver-allocator + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "agones.name" $ }} + chart: {{ template "agones.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} +rules: +- apiGroups: ["allocation.agones.dev"] + resources: ["gameserverallocations"] + verbs: ["create"] + +--- +# Create a ServiceAccount that will be bound to the above role +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gameserver-allocator + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "agones.name" $ }} + chart: {{ template "agones.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} + +--- +# Bind the gameserver-allocator ServiceAccount to the gameserver-allocator Role +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gameserver-allocator + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "agones.name" $ }} + chart: {{ template "agones.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} +subjects: +- kind: ServiceAccount + name: gameserver-allocator + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: gameserver-allocator + +{{- end }} + +--- +# Allocation CA +{{- $ca := genCA "allocation-ca" 3650 }} +apiVersion: v1 +kind: Secret +metadata: + name: allocator-client-ca + labels: + app: {{ template "agones.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: +{{- if .Values.agones.allocator.generateTLS }} + client-ca.crt: {{ b64enc $ca.Cert }} +{{- else }} + {{- (.Files.Glob "certs/allocator/client-ca/*").AsSecrets | nindent 2 }} +{{- end }} + +--- +# Allocation TLS certs +{{- $cert := genSignedCert "" nil nil 3650 $ca }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: allocator-tls + labels: + app: {{ template "agones.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: +{{- if .Values.agones.allocator.generateTLS }} + tls.crt: {{ b64enc $cert.Cert }} + tls.key: {{ b64enc $cert.Key }} +{{- else }} + tls.crt: {{ .Files.Get "certs/allocator/server.crt" | b64enc }} + tls.key: {{ .Files.Get "certs/allocator/server.key" | b64enc }} +{{- end }} + +--- +# Allocation TLS CA +apiVersion: v1 +kind: Secret +metadata: + name: allocator-tls-ca + labels: + app: {{ template "agones.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: +{{- if .Values.agones.allocator.generateTLS }} + tls-ca.crt: {{ b64enc $ca.Cert }} +{{- else }} + tls-ca.crt: {{ .Files.Get "certs/allocator/server.crt" | b64enc }} +{{- end }} \ No newline at end of file diff --git a/install/helm/agones/values.yaml b/install/helm/agones/values.yaml index b13078fafe..59be2a934c 100644 --- a/install/helm/agones/values.yaml +++ b/install/helm/agones/values.yaml @@ -95,6 +95,20 @@ agones: periodSeconds: 3 failureThreshold: 3 timeoutSeconds: 1 + allocator: + install: true + tolerations: + - key: "stable.agones.dev/agones-system" + operator: "Equal" + value: "true" + effect: "NoExecute" + replicas: 3 + http: + expose: true + response: ok + port: 443 + serviceType: LoadBalancer + generateTLS: true image: registry: gcr.io/agones-images tag: 0.11.0 @@ -109,6 +123,10 @@ agones: ping: name: agones-ping pullPolicy: IfNotPresent + allocator: + name: agones-allocator + pullPolicy: IfNotPresent + gameservers: namespaces: diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index c8656f0d3d..f9ff33c6de 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -991,6 +991,193 @@ spec: - name: web port: 8080 --- +# Source: agones/templates/service/allocation.yaml +# Copyright 2019 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Define a Service for the gameserver-allocator +apiVersion: v1 +kind: Service +metadata: + name: gameserver-allocator + namespace: agones-system + labels: + component: allocator + app: agones + chart: agones-0.10.0 + release: agones-manual + heritage: Tiller +spec: + selector: + multicluster.agones.dev/role: allocator + ports: + - port: 443 + name: https + targetPort: 8443 + protocol: TCP + type: LoadBalancer + +--- +# Deploy a pod to run the gameserver-allocator code +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gameserver-allocator + namespace: agones-system + labels: + multicluster.agones.dev/role: allocator + app: agones + release: agones-manual + heritage: Tiller +spec: + replicas: 3 + selector: + matchLabels: + multicluster.agones.dev/role: allocator + app: agones + release: agones-manual + heritage: Tiller + template: + metadata: + labels: + multicluster.agones.dev/role: allocator + app: agones + release: agones-manual + heritage: Tiller + spec: + serviceAccountName: gameserver-allocator + volumes: + - name: tls + secret: + secretName: allocator-tls + - name: client-ca + secret: + secretName: allocator-client-ca + containers: + - name: agones-allocator + image: "gcr.io/agones-images/agones-allocator:0.10.0" + imagePullPolicy: IfNotPresent + ports: + - name: https + containerPort: 8443 + volumeMounts: + - mountPath: /home/allocator/tls + name: tls + readOnly: true + - mountPath: /home/allocator/client-ca + name: client-ca + readOnly: true + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + +--- +# Create a Role in the default namespace that grants access to the agones api +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gameserver-allocator + namespace: agones-system + labels: + app: agones + chart: agones-0.10.0 + release: agones-manual + heritage: Tiller +rules: +- apiGroups: ["allocation.agones.dev"] + resources: ["gameserverallocations"] + verbs: ["create"] + +--- +# Create a ServiceAccount that will be bound to the above role +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gameserver-allocator + namespace: agones-system + labels: + app: agones + chart: agones-0.10.0 + release: agones-manual + heritage: Tiller + +--- +# Bind the gameserver-allocator ServiceAccount to the gameserver-allocator Role +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gameserver-allocator + namespace: agones-system + labels: + app: agones + chart: agones-0.10.0 + release: agones-manual + heritage: Tiller +subjects: +- kind: ServiceAccount + name: gameserver-allocator + namespace: agones-system +roleRef: + kind: Role + name: gameserver-allocator + +--- +# Allocation CA +apiVersion: v1 +kind: Secret +metadata: + name: allocator-client-ca + labels: + app: agones-manual + chart: "agones-0.10.0" + release: "agones-manual" + heritage: "Tiller" +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR5ekNDQXJPZ0F3SUJBZ0lVS2JaV1IwSmk2QlRxa2RiQjkwT200dUpIaTM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNRVlV4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTQpHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpERXVNQ3dHQ1NxR1NJYjNEUUVKQVJZZllXZHZibVZ6CkxXUnBjMk4xYzNOQVoyOXZaMnhsWjNKdmRYQnpMbU52YlRBZUZ3MHhPVEExTVRReE9ETXhOVGxhRncweU9UQTEKTVRFeE9ETXhOVGxhTUhVeEN6QUpCZ05WQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFdwpId1lEVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReExqQXNCZ2txaGtpRzl3MEJDUUVXCkgyRm5iMjVsY3kxa2FYTmpkWE56UUdkdmIyZHNaV2R5YjNWd2N5NWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURldHlVTHdOdWpGYlhadEdGY09DQXdUWWZVNTRENEd3ZGsxdDdSV3FhNQovL3JpS3N3cWtjcnc2OGxOa0lObSt5TU5nRmtkdDJ6Z2x3akNnakpaWGNGb3ZQLzcvZzMyQ041VzZneXg3Y0IwCkZUWEpWV2UzdVVHNlBXNVlQYUgxMkN1Y0hDcTVpTnp3bEV5RFVQQ0pBYVBPNWZVQmxiQ1JVTFlQaEJuSlhaWHIKQjZ6ZHRjNlIxdXlrWE5mVmhaWUh2SVZyNW9XSUZ5Z0NObmp2TWdIbWt2ZkxOeGltU2dwc0tYeUxqVzExU1d3eApteHBwb3drbHdLMlFxYXhuYVNPVFl2N3VjbUwwTzhYRTg5bi9TbzFZbklPZ2RReU5tTmFVSjRENk1oeHZLZUpBClRmZzZrOTFRd1c5K0VJaWh0K05qQ01zcHg0bjZ5bGlSbm8xR01Ic1pJamJIQWdNQkFBR2pVekJSTUIwR0ExVWQKRGdRV0JCUXhqcnJ0S3BOcE02TUg5ODY4cTY1ZHE0NzcwVEFmQmdOVkhTTUVHREFXZ0JReGpycnRLcE5wTTZNSAo5ODY4cTY1ZHE0NzcwVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNvCmNGc3hzY0ZOajh3VGt1aHZtRUcyODV6Q3M5OFFqR3FzT1RCNC9Ha3JUbWJOUW9xT2JuYzZpTkM0YnM5QUM2SE8KYWJOUkM2bTVVYVZRVkgzVUI2TzMrRm00MFROb1RadDFHQkYzL0x0ZHdnZFJrbUZUaGlCL2ZJdHRUMFBHYUVmZQpBK3lleWhOYlZ5VWdPWUk3bks1UEs1czdnQlN5a0dMelhJaWVsb21tckE2SzI5L1pObGoraE53S1dISUJaL0NuCmNPa1ZnUEJrZUJrWStWclZjM24vSEJjbGorczdSNzVLcmhwTktPTWZ2M2pvcmhSQVE4ZVFvSWhIYmlxY2JNN3EKbHpwbDM5VXhzajJWMVlCYXl1OWlubXFKaUJmNFNNdW50elVwUHpLYWwwTytpZUtBMmJLU1NUbEdHbnIzUlJ2eQpNWGRnN1pYWmV3S3FiYlMvQnJIcQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + + +--- +# Allocation TLS certs +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: allocator-tls + labels: + app: agones-manual + chart: "agones-0.10.0" + release: "agones-manual" + heritage: "Tiller" +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR5ekNDQXJPZ0F3SUJBZ0lVS2JaV1IwSmk2QlRxa2RiQjkwT200dUpIaTM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNRVlV4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTQpHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpERXVNQ3dHQ1NxR1NJYjNEUUVKQVJZZllXZHZibVZ6CkxXUnBjMk4xYzNOQVoyOXZaMnhsWjNKdmRYQnpMbU52YlRBZUZ3MHhPVEExTVRReE9ETXhOVGxhRncweU9UQTEKTVRFeE9ETXhOVGxhTUhVeEN6QUpCZ05WQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFdwpId1lEVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReExqQXNCZ2txaGtpRzl3MEJDUUVXCkgyRm5iMjVsY3kxa2FYTmpkWE56UUdkdmIyZHNaV2R5YjNWd2N5NWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURldHlVTHdOdWpGYlhadEdGY09DQXdUWWZVNTRENEd3ZGsxdDdSV3FhNQovL3JpS3N3cWtjcnc2OGxOa0lObSt5TU5nRmtkdDJ6Z2x3akNnakpaWGNGb3ZQLzcvZzMyQ041VzZneXg3Y0IwCkZUWEpWV2UzdVVHNlBXNVlQYUgxMkN1Y0hDcTVpTnp3bEV5RFVQQ0pBYVBPNWZVQmxiQ1JVTFlQaEJuSlhaWHIKQjZ6ZHRjNlIxdXlrWE5mVmhaWUh2SVZyNW9XSUZ5Z0NObmp2TWdIbWt2ZkxOeGltU2dwc0tYeUxqVzExU1d3eApteHBwb3drbHdLMlFxYXhuYVNPVFl2N3VjbUwwTzhYRTg5bi9TbzFZbklPZ2RReU5tTmFVSjRENk1oeHZLZUpBClRmZzZrOTFRd1c5K0VJaWh0K05qQ01zcHg0bjZ5bGlSbm8xR01Ic1pJamJIQWdNQkFBR2pVekJSTUIwR0ExVWQKRGdRV0JCUXhqcnJ0S3BOcE02TUg5ODY4cTY1ZHE0NzcwVEFmQmdOVkhTTUVHREFXZ0JReGpycnRLcE5wTTZNSAo5ODY4cTY1ZHE0NzcwVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNvCmNGc3hzY0ZOajh3VGt1aHZtRUcyODV6Q3M5OFFqR3FzT1RCNC9Ha3JUbWJOUW9xT2JuYzZpTkM0YnM5QUM2SE8KYWJOUkM2bTVVYVZRVkgzVUI2TzMrRm00MFROb1RadDFHQkYzL0x0ZHdnZFJrbUZUaGlCL2ZJdHRUMFBHYUVmZQpBK3lleWhOYlZ5VWdPWUk3bks1UEs1czdnQlN5a0dMelhJaWVsb21tckE2SzI5L1pObGoraE53S1dISUJaL0NuCmNPa1ZnUEJrZUJrWStWclZjM24vSEJjbGorczdSNzVLcmhwTktPTWZ2M2pvcmhSQVE4ZVFvSWhIYmlxY2JNN3EKbHpwbDM5VXhzajJWMVlCYXl1OWlubXFKaUJmNFNNdW50elVwUHpLYWwwTytpZUtBMmJLU1NUbEdHbnIzUlJ2eQpNWGRnN1pYWmV3S3FiYlMvQnJIcQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBd3Z6cE5TbFZ2b2NoZ2ZGdWo5Nk9kSWdNeXJ2UWFycmhVL0pQTXJQN0d6NzFPVTduCnd4aCsxMWpsdUZLZVFMUE1ST21RN0FOek1DdU9rNG9KaUFmWEZSbHlVeGl4L2JuMEpHbGVrY2ZCUlNpelRVVXAKd24vaFpoVWF0ZHpNMW5IbmJiaGV6eUR0eGVBdFdRcm9FV0NwU0dVNmIyRkZ4bjNPK3IxbmM0UXRtdUUxQytLRAp3NXFVeVp0eHNoRFM5N3YzeDh6dTkyQklqQ0ZJVHk4TnIxdDRYYzlmbWd6RlhSdGE1Z3NJcHBYY05IUEVSbzMvCjNaU3BWeDVxMEhPZ2o5SGEzM01sRDVZNGNSdW1ybzJuUVA2c20rNzIvWHdMMC9ONXNVVDlpZFIvZUFON2RabG4KcGZqLzlkcHpZRFJ4UE9IZXNwbDE5dFB2dE9YMlBjd3IzRGdGWndJREFRQUJBb0lCQUVvekNkK1VBakFGcGllWApvellXUCtseVdFUFJvcWJxeEpJNFZCRDhVbUwwMjBaYWszRTNZaFU2bS9nL1lEOUk3RU9SQ2ZmMS9DQnBndEQ4CkoxSmZnV1pqRDVFMWhZNEM5RGZpN1B6V29lSmFjWmpHNS9RK3k1d1lEZGtFMCtJSzZFRmxkcGFsZGpYbGIveEIKWFR3M3AxT0lTZXRXZmNIaDRDQ2tjTGlScFBVWmNZM1k2SmkwV0RrbGFia2FhRkduMkdCWTZhdFVIQXFTUlg1LwpDdnhOYTlHTFVTSkNYd1FMTkVTMWpWb2NqQ2ZicTM4Zlp0cjlZelRGNlZTYnB5MGM3UnA0RFBtUlhPbEVEc1RxCjB4dmFsdlZsNW80dUhGTGhnSlhLdkZ0NUxXMWU2Y3JTTHpNR0N5K0VReTBvWHZ5dDgyaUNMTXpMWWRnMVVyZDgKcGszZEZJa0NnWUVBOGR4YlltNURzM1pjenhQOGFyamhVRU91SWhFOTVCWmpwWE1WVGVYQ1VzOFdoeWFyZ0RNQgpuczVwNlJYeVo0WWl5U2V6N2F5NnRod1QvSk5rdDVjc3RxcFcxY2NRaC93aVFjWmdWckJPdmdrMTZUVWFEbzNyCmlsbFNtRm81cTYxeEpxYXo2SVZrUWJXRXVDMWp5RGVSVXlNSk9OS05HQVVoUnVnemxLb3hOODBDZ1lFQXptTVEKVyt1QjRaWkhkdW4xSlZPNHBWdkQ5RjJYU1hkVUlhQU9DQ1JyVCtGeVdUV2tDcDVlMWZpci9reW5kTmRMMHdIQQpZTHdsUTBQNit3eVErd3pvUUlqeGtsNkppMFU5OXQycmRpdGFrZGxlVFJEZ3dldlFNNEs5RVJwUjRvbGFzV3dUCk9pZC9SdHp4c1BXK0lWNGVRWHZHMWVBcWtPY3RZNThQS2cvNzFnTUNnWUVBdUErZDBFdmY0K3k0OXJMY0R4QUMKRUlaaE4vWHRKdTdQQ0xPTFRBd0ZsZVdaM0dMV2M2cmRtZEMya3pjcmtoTm4xN1FLWDE5cmZIbmp6LzBQMGhRUAphRGc3KzFjU1FwcmtLekJnVXJpOFJDMVlSTkV2c2xIWkppR1NGUFhkRzhUVDdybEJBenk0cnJVSWV1MjRHVUFJCnJiOWxIa1JHTWpoOU9hd3V6UDIwbFNVQ2dZQTRlSXFsWEE4eHRuamZaS3liQmdsajFYUU1qUDgwTHQ1cVJPMFIKOVVVWG1rdzdkSjNwK2VOaEVLV2U4MEp1aWVwSlZoVlpZcUdmZ2g1T1N1WHdpMXVSTE9kTWNXbVpIZDBTaXh5NwpQS21QZHJhQ3g3ZDMyQm9qdmdZVmcrb2IzaEZlc24rZ1RlWlpjV01BVkgwdGpCOGpNOGI0QmxvZlErSDdmQzRlCkNEbVVHUUtCZ0VGeEduOHlWNXR4NmVnUjJOOWx3aHl3b0M4cVVyUWE1S2VFNmNBVTFrNGNwck1xUUVyaVJ3SVIKR2Y4MHEyNWFBbmhGdkU0Tis3cU5FaXJDK2tpYXczK0RDQ21zUStybmxjNlRyczU2MGZIc3FJU0JUai9pOVgwcgptZDJiaHBiYXo4WXVNVm0wcGI5L2M0U1ppdkJ6NjR5SzJPYjRzbkZXMGZMa0xRUStwaks1Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + +--- +# Allocation TLS CA +apiVersion: v1 +kind: Secret +metadata: + name: allocator-tls-ca + labels: + app: agones-manual + chart: "agones-0.10.0" + release: "agones-manual" + heritage: "Tiller" +data: + tls-ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR5ekNDQXJPZ0F3SUJBZ0lVS2JaV1IwSmk2QlRxa2RiQjkwT200dUpIaTM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNRVlV4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTQpHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpERXVNQ3dHQ1NxR1NJYjNEUUVKQVJZZllXZHZibVZ6CkxXUnBjMk4xYzNOQVoyOXZaMnhsWjNKdmRYQnpMbU52YlRBZUZ3MHhPVEExTVRReE9ETXhOVGxhRncweU9UQTEKTVRFeE9ETXhOVGxhTUhVeEN6QUpCZ05WQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFdwpId1lEVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReExqQXNCZ2txaGtpRzl3MEJDUUVXCkgyRm5iMjVsY3kxa2FYTmpkWE56UUdkdmIyZHNaV2R5YjNWd2N5NWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURldHlVTHdOdWpGYlhadEdGY09DQXdUWWZVNTRENEd3ZGsxdDdSV3FhNQovL3JpS3N3cWtjcnc2OGxOa0lObSt5TU5nRmtkdDJ6Z2x3akNnakpaWGNGb3ZQLzcvZzMyQ041VzZneXg3Y0IwCkZUWEpWV2UzdVVHNlBXNVlQYUgxMkN1Y0hDcTVpTnp3bEV5RFVQQ0pBYVBPNWZVQmxiQ1JVTFlQaEJuSlhaWHIKQjZ6ZHRjNlIxdXlrWE5mVmhaWUh2SVZyNW9XSUZ5Z0NObmp2TWdIbWt2ZkxOeGltU2dwc0tYeUxqVzExU1d3eApteHBwb3drbHdLMlFxYXhuYVNPVFl2N3VjbUwwTzhYRTg5bi9TbzFZbklPZ2RReU5tTmFVSjRENk1oeHZLZUpBClRmZzZrOTFRd1c5K0VJaWh0K05qQ01zcHg0bjZ5bGlSbm8xR01Ic1pJamJIQWdNQkFBR2pVekJSTUIwR0ExVWQKRGdRV0JCUXhqcnJ0S3BOcE02TUg5ODY4cTY1ZHE0NzcwVEFmQmdOVkhTTUVHREFXZ0JReGpycnRLcE5wTTZNSAo5ODY4cTY1ZHE0NzcwVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNvCmNGc3hzY0ZOajh3VGt1aHZtRUcyODV6Q3M5OFFqR3FzT1RCNC9Ha3JUbWJOUW9xT2JuYzZpTkM0YnM5QUM2SE8KYWJOUkM2bTVVYVZRVkgzVUI2TzMrRm00MFROb1RadDFHQkYzL0x0ZHdnZFJrbUZUaGlCL2ZJdHRUMFBHYUVmZQpBK3lleWhOYlZ5VWdPWUk3bks1UEs1czdnQlN5a0dMelhJaWVsb21tckE2SzI5L1pObGoraE53S1dISUJaL0NuCmNPa1ZnUEJrZUJrWStWclZjM24vSEJjbGorczdSNzVLcmhwTktPTWZ2M2pvcmhSQVE4ZVFvSWhIYmlxY2JNN3EKbHpwbDM5VXhzajJWMVlCYXl1OWlubXFKaUJmNFNNdW50elVwUHpLYWwwTytpZUtBMmJLU1NUbEdHbnIzUlJ2eQpNWGRnN1pYWmV3S3FiYlMvQnJIcQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== +--- # Source: agones/templates/controller.yaml # Copyright 2018 Google LLC All Rights Reserved. # diff --git a/pkg/gameserverallocations/controller.go b/pkg/gameserverallocations/controller.go index e4a547b30f..5bf1824af9 100644 --- a/pkg/gameserverallocations/controller.go +++ b/pkg/gameserverallocations/controller.go @@ -70,8 +70,8 @@ var ( ) const ( - secretClientCertName = "client.crt" - secretClientKeyName = "client.key" + secretClientCertName = "tls.crt" + secretClientKeyName = "tls.key" secretCaCertName = "ca.crt" ) @@ -87,7 +87,9 @@ type Controller struct { gameServerGetter getterv1alpha1.GameServersGetter gameServerLister listerv1alpha1.GameServerLister allocationPolicyLister multiclusterlisterv1alpha1.GameServerAllocationPolicyLister + allocationPolicySynced cache.InformerSynced secretLister corev1lister.SecretLister + secretSynced cache.InformerSynced stop <-chan struct{} workerqueue *workerqueue.WorkerQueue recorder record.EventRecorder @@ -124,7 +126,9 @@ func NewController(apiServer *apiserver.APIServer, gameServerGetter: agonesClient.StableV1alpha1(), gameServerLister: agonesInformer.GameServers().Lister(), allocationPolicyLister: agonesInformerFactory.Multicluster().V1alpha1().GameServerAllocationPolicies().Lister(), + allocationPolicySynced: agonesInformerFactory.Multicluster().V1alpha1().GameServerAllocationPolicies().Informer().HasSynced, secretLister: kubeInformerFactory.Core().V1().Secrets().Lister(), + secretSynced: kubeInformerFactory.Core().V1().Secrets().Informer().HasSynced, } c.baseLogger = runtime.NewLoggerWithType(c) c.workerqueue = workerqueue.NewWorkerQueue(c.syncGameServers, c.baseLogger, logfields.GameServerKey, stable.GroupName+".GameServerUpdateController") @@ -177,7 +181,7 @@ func (c *Controller) registerAPIResource(api *apiserver.APIServer) { func (c *Controller) Run(_ int, stop <-chan struct{}) error { c.stop = stop c.baseLogger.Info("Wait for cache sync") - if !cache.WaitForCacheSync(stop, c.gameServerSynced) { + if !cache.WaitForCacheSync(stop, c.gameServerSynced, c.secretSynced, c.allocationPolicySynced) { return errors.New("failed to wait for caches to sync") } diff --git a/pkg/gameserverallocations/controller_test.go b/pkg/gameserverallocations/controller_test.go index 5773a9b926..73a9989263 100644 --- a/pkg/gameserverallocations/controller_test.go +++ b/pkg/gameserverallocations/controller_test.go @@ -75,7 +75,7 @@ func TestControllerAllocationHandler(t *testing.T) { return true, gs, nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -254,7 +254,7 @@ func TestControllerAllocatePriority(t *testing.T) { return true, gs, nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -646,7 +646,7 @@ func TestControllerRunCacheSync(t *testing.T) { m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(watch, nil)) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.gameServerSynced) defer cancel() assertCacheEntries := func(expected int) { @@ -759,7 +759,7 @@ func TestMultiClusterAllocationFromLocal(t *testing.T) { }, nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.allocationPolicySynced, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -805,7 +805,7 @@ func TestMultiClusterAllocationFromLocal(t *testing.T) { }, nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.allocationPolicySynced, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -893,7 +893,7 @@ func TestMultiClusterAllocationFromRemote(t *testing.T) { return true, getTestSecret(secretName, server.TLS.Certificates[0].Certificate[0]), nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.allocationPolicySynced, c.secretSynced, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -968,7 +968,7 @@ func TestMultiClusterAllocationFromRemote(t *testing.T) { return true, getTestSecret(secretName, server.TLS.Certificates[0].Certificate[0]), nil }) - stop, cancel := agtesting.StartInformers(m) + stop, cancel := agtesting.StartInformers(m, c.allocationPolicySynced, c.secretSynced, c.gameServerSynced) defer cancel() // This call initializes the cache @@ -1014,7 +1014,7 @@ func TestCreateRestClientError(t *testing.T) { return true, &corev1.SecretList{ Items: []corev1.Secret{{ Data: map[string][]byte{ - "client.crt": clientCert, + "tls.crt": clientCert, }, ObjectMeta: metav1.ObjectMeta{ Name: "secret-name", @@ -1023,7 +1023,7 @@ func TestCreateRestClientError(t *testing.T) { }}}, nil }) - _, cancel := agtesting.StartInformers(m) + _, cancel := agtesting.StartInformers(m, c.secretSynced) defer cancel() _, err := c.createRemoteClusterRestClient(defaultNs, "secret-name") @@ -1038,8 +1038,8 @@ func TestCreateRestClientError(t *testing.T) { return true, &corev1.SecretList{ Items: []corev1.Secret{{ Data: map[string][]byte{ - "client.crt": []byte("XXX"), - "client.key": []byte("XXX"), + "tls.crt": []byte("XXX"), + "tls.key": []byte("XXX"), }, ObjectMeta: metav1.ObjectMeta{ Name: "secret-name", @@ -1048,7 +1048,7 @@ func TestCreateRestClientError(t *testing.T) { }}}, nil }) - _, cancel := agtesting.StartInformers(m) + _, cancel := agtesting.StartInformers(m, c.secretSynced) defer cancel() _, err := c.createRemoteClusterRestClient(defaultNs, "secret-name") @@ -1063,7 +1063,7 @@ func TestCreateRestClientError(t *testing.T) { return true, getTestSecret("secret-name", []byte("XXX")), nil }) - _, cancel := agtesting.StartInformers(m) + _, cancel := agtesting.StartInformers(m, c.secretSynced) defer cancel() _, err := c.createRemoteClusterRestClient(defaultNs, "secret-name") @@ -1159,9 +1159,9 @@ func getTestSecret(secretName string, serverCert []byte) *corev1.SecretList { Items: []corev1.Secret{ { Data: map[string][]byte{ - "ca.crt": serverCert, - "client.key": clientKey, - "client.crt": clientCert, + "ca.crt": serverCert, + "tls.key": clientKey, + "tls.crt": clientCert, }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index 9322d786d3..b2b0b6d89d 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -70,6 +70,7 @@ type Controller struct { gameServerLister listerv1alpha1.GameServerLister gameServerSynced cache.InformerSynced nodeLister corelisterv1.NodeLister + nodeSynced cache.InformerSynced portAllocator *PortAllocator healthController *HealthController workerqueue *workerqueue.WorkerQueue @@ -114,6 +115,7 @@ func NewController( gameServerLister: gameServers.Lister(), gameServerSynced: gsInformer.HasSynced, nodeLister: kubeInformerFactory.Core().V1().Nodes().Lister(), + nodeSynced: kubeInformerFactory.Core().V1().Nodes().Informer().HasSynced, portAllocator: NewPortAllocator(minPort, maxPort, kubeInformerFactory, agonesInformerFactory), healthController: NewHealthController(kubeClient, agonesClient, kubeInformerFactory, agonesInformerFactory), } @@ -298,7 +300,7 @@ func (c *Controller) Run(workers int, stop <-chan struct{}) error { } c.baseLogger.Info("Wait for cache sync") - if !cache.WaitForCacheSync(stop, c.gameServerSynced, c.podSynced) { + if !cache.WaitForCacheSync(stop, c.gameServerSynced, c.podSynced, c.nodeSynced) { return errors.New("failed to wait for caches to sync") } @@ -729,8 +731,7 @@ func (c *Controller) syncGameServerRequestReadyState(gs *v1alpha1.GameServer) (* // syncGameServerShutdownState deletes the GameServer (and therefore the backing Pod) if it is in shutdown state func (c *Controller) syncGameServerShutdownState(gs *v1alpha1.GameServer) error { - if !gs.ObjectMeta.DeletionTimestamp.IsZero() || - (gs.Status.State != v1alpha1.GameServerStateShutdown && gs.Status.State != v1alpha1.GameServerStateUnhealthy) { + if !(gs.Status.State == v1alpha1.GameServerStateShutdown && gs.ObjectMeta.DeletionTimestamp.IsZero()) { return nil } diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 83013df22c..c794624329 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -944,7 +944,7 @@ func TestControllerSyncGameServerRequestReadyState(t *testing.T) { return true, gs, nil }) - _, cancel := agtesting.StartInformers(m, c.gameServerSynced) + _, cancel := agtesting.StartInformers(m, c.podSynced) defer cancel() gs, err := c.syncGameServerRequestReadyState(gsFixture) @@ -983,7 +983,7 @@ func TestControllerSyncGameServerRequestReadyState(t *testing.T) { return true, gs, nil }) - _, cancel := agtesting.StartInformers(m, c.gameServerSynced) + _, cancel := agtesting.StartInformers(m, c.podSynced, c.nodeSynced) defer cancel() gs, err := c.syncGameServerRequestReadyState(gsFixture) diff --git a/pkg/gameservers/health.go b/pkg/gameservers/health.go index 2c028b5b84..9bfb25eaa0 100644 --- a/pkg/gameservers/health.go +++ b/pkg/gameservers/health.go @@ -164,8 +164,7 @@ func (hc *HealthController) syncGameServer(key string) error { } // at this point we don't care, we're already Unhealthy / deleting - if !gs.ObjectMeta.DeletionTimestamp.IsZero() || gs.Status.State == v1alpha1.GameServerStateShutdown || - gs.Status.State == v1alpha1.GameServerStateUnhealthy { + if gs.IsBeingDeleted() || gs.Status.State == v1alpha1.GameServerStateUnhealthy { return nil } diff --git a/pkg/gameserversets/controller.go b/pkg/gameserversets/controller.go index c3a879edda..c98ab87d21 100644 --- a/pkg/gameserversets/controller.go +++ b/pkg/gameserversets/controller.go @@ -467,7 +467,7 @@ func computeReconciliationAction(strategy apis.SchedulingStrategy, list []*v1alp toDelete = append(toDelete, potentialDeletions[0:deleteCount]...) } - if deleteCount > maxDeletions { + if len(toDelete) > maxDeletions { toDelete = toDelete[0:maxDeletions] partialReconciliation = true } diff --git a/pkg/gameserversets/controller_test.go b/pkg/gameserversets/controller_test.go index d4ace95659..1a96dc4151 100644 --- a/pkg/gameserversets/controller_test.go +++ b/pkg/gameserversets/controller_test.go @@ -157,6 +157,44 @@ func TestComputeReconciliationAction(t *testing.T) { wantNumServersToAdd: 0, wantIsPartial: true, }, + { + desc: "DeletingUnhealthyGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateUnhealthy), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 2, + }, + { + desc: "DeletingErrorGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateError), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 2, + }, + { + desc: "DeletingPartialGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 3, + wantIsPartial: true, + }, } for _, tc := range cases { diff --git a/pkg/sdkserver/sdkserver.go b/pkg/sdkserver/sdkserver.go index 4a68549b00..d3d72d161f 100644 --- a/pkg/sdkserver/sdkserver.go +++ b/pkg/sdkserver/sdkserver.go @@ -252,6 +252,12 @@ func (s *SDKServer) updateState() error { return err } + // if we are currently in shutdown/being deleted, there is no escaping + if gs.IsBeingDeleted() { + s.logger.Info("GameServerState being shutdown. Skipping update.") + return nil + } + // if the state is currently unhealthy, you can't go back to Ready if gs.Status.State == stablev1alpha1.GameServerStateUnhealthy { s.logger.Info("GameServerState already unhealthy. Skipping update.") @@ -484,7 +490,7 @@ func (s *SDKServer) sendGameServerUpdate(gs *stablev1alpha1.GameServer) { func (s *SDKServer) runHealth() { s.checkHealth() if !s.healthy() { - s.logger.WithField("gameServerName", s.gameServerName).Info("being marked as not healthy") + s.logger.WithField("gameServerName", s.gameServerName).Info("has failed health check") s.enqueueState(stablev1alpha1.GameServerStateUnhealthy) } } diff --git a/pkg/sdkserver/sdkserver_test.go b/pkg/sdkserver/sdkserver_test.go index d47d91dd8f..70ddd99924 100644 --- a/pkg/sdkserver/sdkserver_test.go +++ b/pkg/sdkserver/sdkserver_test.go @@ -295,37 +295,62 @@ func TestSDKServerSyncGameServer(t *testing.T) { func TestSidecarUpdateState(t *testing.T) { t.Parallel() - t.Run("ignore state change when unhealthy", func(t *testing.T) { - m := agtesting.NewMocks() - sc, err := defaultSidecar(m) - assert.Nil(t, err) - sc.gsState = v1alpha1.GameServerStateReady + fixtures := map[string]struct { + f func(gs *v1alpha1.GameServer) + }{ + "unhealthy": { + f: func(gs *v1alpha1.GameServer) { + gs.Status.State = v1alpha1.GameServerStateUnhealthy + }, + }, + "shutdown": { + f: func(gs *v1alpha1.GameServer) { + gs.Status.State = v1alpha1.GameServerStateShutdown + }, + }, + "deleted": { + f: func(gs *v1alpha1.GameServer) { + now := metav1.Now() + gs.ObjectMeta.DeletionTimestamp = &now + }, + }, + } - updated := false + for k, v := range fixtures { + t.Run(k, func(t *testing.T) { + m := agtesting.NewMocks() + sc, err := defaultSidecar(m) + assert.Nil(t, err) + sc.gsState = v1alpha1.GameServerStateReady - m.AgonesClient.AddReactor("list", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { - gs := v1alpha1.GameServer{ - ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace}, - Status: v1alpha1.GameServerStatus{ - State: v1alpha1.GameServerStateUnhealthy, - }, - } - return true, &v1alpha1.GameServerList{Items: []v1alpha1.GameServer{gs}}, nil - }) - m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { - updated = true - return true, nil, nil - }) + updated := false - stop := make(chan struct{}) - defer close(stop) - sc.informerFactory.Start(stop) - assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) + m.AgonesClient.AddReactor("list", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { + gs := v1alpha1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace}, + Status: v1alpha1.GameServerStatus{}, + } - err = sc.updateState() - assert.Nil(t, err) - assert.False(t, updated) - }) + // apply mutation + v.f(&gs) + + return true, &v1alpha1.GameServerList{Items: []v1alpha1.GameServer{gs}}, nil + }) + m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { + updated = true + return true, nil, nil + }) + + stop := make(chan struct{}) + defer close(stop) + sc.informerFactory.Start(stop) + assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) + + err = sc.updateState() + assert.Nil(t, err) + assert.False(t, updated) + }) + } } func TestSidecarHealthLastUpdated(t *testing.T) { diff --git a/sdks/nodejs/spec/agonesSDK.spec.js b/sdks/nodejs/spec/agonesSDK.spec.js index a6f09d35bc..396f2fb7d8 100644 --- a/sdks/nodejs/spec/agonesSDK.spec.js +++ b/sdks/nodejs/spec/agonesSDK.spec.js @@ -10,6 +10,31 @@ describe('agones', () => { agonesSDK = new AgonesSDK(); }); + describe('allocate', () => { + it('calls the server and handles success', async () => { + spyOn(agonesSDK.client, 'allocate').and.callFake((request, callback) => { + let result = new messages.Empty(); + callback(undefined, result); + }); + let result = await agonesSDK.allocate(); + expect(agonesSDK.client.allocate).toHaveBeenCalled(); + expect(result).toEqual({}); + }); + + it('calls the server and handles failure', async () => { + spyOn(agonesSDK.client, 'allocate').and.callFake((request, callback) => { + callback('error', undefined); + }); + try { + await agonesSDK.allocate(); + fail(); + } catch (error) { + expect(agonesSDK.client.allocate).toHaveBeenCalled(); + expect(error).toEqual('error'); + } + }); + }); + describe('ready', () => { it('calls the server and handles success', async () => { spyOn(agonesSDK.client, 'ready').and.callFake((request, callback) => { @@ -243,4 +268,11 @@ describe('agones', () => { } }); }); + describe('close', () => { + it('closes the client connection when called', async () => { + spyOn(agonesSDK.client, 'close').and.callFake(()=>{}); + await agonesSDK.close(); + expect(agonesSDK.client.close).toHaveBeenCalled(); + }); + }); }); diff --git a/sdks/nodejs/src/agonesSDK.js b/sdks/nodejs/src/agonesSDK.js index 612d2184e5..1b2149aa65 100644 --- a/sdks/nodejs/src/agonesSDK.js +++ b/sdks/nodejs/src/agonesSDK.js @@ -6,10 +6,32 @@ const services = require('../lib/sdk_grpc_pb'); class AgonesSDK { constructor() { this.client = new services.SDKClient('localhost:59357', grpc.credentials.createInsecure()); + this.healthStream = undefined; + this.emitters = []; + } + async close(){ + if (this.healthStream !== undefined){ + this.healthStream.destroy() + } + this.emitters.forEach(emitter => emitter.call.cancel()); + this.client.close(); + } + + async allocate() { + const request = new messages.Empty(); + return new Promise((resolve, reject) => { + this.client.allocate(request, (error, response) => { + if (error) { + reject(error); + } else { + resolve(response.toObject()); + } + }); + }); } async ready() { - let request = new messages.Empty(); + const request = new messages.Empty(); return new Promise((resolve, reject) => { this.client.ready(request, (error, response) => { if (error) { @@ -20,9 +42,8 @@ class AgonesSDK { }); }); } - async shutdown() { - let request = new messages.Empty(); + const request = new messages.Empty(); return new Promise((resolve, reject) => { this.client.shutdown(request, (error, response) => { if (error) { @@ -33,19 +54,17 @@ class AgonesSDK { }); }); } - health() { - if (!this.healthStream) { + if (this.healthStream === undefined) { this.healthStream = this.client.health((error) => { // Ignore error as this can't be caught }); } - let request = new messages.Empty(); + const request = new messages.Empty(); this.healthStream.write(request); } - async getGameServer() { - let request = new messages.Empty(); + const request = new messages.Empty(); return new Promise((resolve, reject) => { this.client.getGameServer(request, (error, response) => { if (error) { @@ -56,17 +75,21 @@ class AgonesSDK { }); }); } - watchGameServer(callback) { - let request = new messages.Empty(); - let emitter = this.client.watchGameServer(request); + const request = new messages.Empty(); + const emitter = this.client.watchGameServer(request); emitter.on('data', (data) => { callback(data.toObject()); }); + emitter.on('error', (error) => { + if (error.code === grpc.status.CANCELLED) { // this happens when call is cancelled + return; + } + }) + this.emitters.push(emitter); } - async setLabel(key, value) { - let request = new messages.KeyValue(); + const request = new messages.KeyValue(); request.setKey(key); request.setValue(value); return new Promise((resolve, reject) => { @@ -79,9 +102,8 @@ class AgonesSDK { }); }); } - async setAnnotation(key, value) { - let request = new messages.KeyValue(); + const request = new messages.KeyValue(); request.setKey(key); request.setValue(value); return new Promise((resolve, reject) => { diff --git a/site/content/en/docs/Advanced/limiting-resources.md b/site/content/en/docs/Advanced/limiting-resources.md index 05fb36ba2d..3c04be0cc0 100644 --- a/site/content/en/docs/Advanced/limiting-resources.md +++ b/site/content/en/docs/Advanced/limiting-resources.md @@ -38,7 +38,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: limit: cpu: "250m" #this is our limit here diff --git a/site/content/en/docs/Advanced/scheduling-and-autoscaling.md b/site/content/en/docs/Advanced/scheduling-and-autoscaling.md index c210ba0e79..7f1790dda1 100644 --- a/site/content/en/docs/Advanced/scheduling-and-autoscaling.md +++ b/site/content/en/docs/Advanced/scheduling-and-autoscaling.md @@ -80,7 +80,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` This is the *default* Fleet scheduling strategy. It is designed for dynamic Kubernetes environments, wherein you wish @@ -135,7 +135,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` This Fleet scheduling strategy is designed for static Kubernetes environments, such as when you are running Kubernetes diff --git a/site/content/en/docs/Advanced/service-accounts.md b/site/content/en/docs/Advanced/service-accounts.md index 6b6b6c2220..bb9bca7b7b 100644 --- a/site/content/en/docs/Advanced/service-accounts.md +++ b/site/content/en/docs/Advanced/service-accounts.md @@ -39,7 +39,7 @@ spec: serviceAccountName: my-special-service-account # a custom service account containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` If a service account is configured, the mounted key is not overwritten, as it assumed that you want to have full control diff --git a/site/content/en/docs/Getting Started/create-fleet.md b/site/content/en/docs/Getting Started/create-fleet.md index 79f31194f7..473bcd79cf 100644 --- a/site/content/en/docs/Getting Started/create-fleet.md +++ b/site/content/en/docs/Getting Started/create-fleet.md @@ -111,7 +111,7 @@ Spec: Creation Timestamp: Spec: Containers: - Image: gcr.io/agones-images/udp-server:0.8 + Image: gcr.io/agones-images/udp-server:0.9 Name: simple-udp Resources: Status: @@ -308,7 +308,7 @@ status: creationTimestamp: null spec: containers: - - image: gcr.io/agones-images/udp-server:0.8 + - image: gcr.io/agones-images/udp-server:0.9 name: simple-udp resources: {} status: diff --git a/site/content/en/docs/Getting Started/create-gameserver.md b/site/content/en/docs/Getting Started/create-gameserver.md index c031c6c4d6..f3381f11f4 100644 --- a/site/content/en/docs/Getting Started/create-gameserver.md +++ b/site/content/en/docs/Getting Started/create-gameserver.md @@ -107,7 +107,7 @@ Spec: Creation Timestamp: Spec: Containers: - Image: gcr.io/agones-images/udp-server:0.8 + Image: gcr.io/agones-images/udp-server:0.9 Name: simple-udp Resources: Limits: diff --git a/site/content/en/docs/Guides/access-api.md b/site/content/en/docs/Guides/access-api.md index c1c84a610e..1cf8476e82 100644 --- a/site/content/en/docs/Guides/access-api.md +++ b/site/content/en/docs/Guides/access-api.md @@ -90,7 +90,7 @@ func main() { Spec: v1alpha1.GameServerSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "udp-server", Image: "gcr.io/agones-images/udp-server:0.8"}}, + Containers: []corev1.Container{{Name: "udp-server", Image: "gcr.io/agones-images/udp-server:0.9"}}, }, }, }, @@ -178,7 +178,7 @@ $ curl http://localhost:8001/apis/stable.agones.dev/v1alpha1/namespaces/default/ "kind": "GameServer", "metadata": { "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"stable.agones.dev/v1alpha1\",\"kind\":\"GameServer\",\"metadata\":{\"annotations\":{},\"name\":\"simple-udp\",\"namespace\":\"default\"},\"spec\":{\"containerPort\":7654,\"hostPort\":7777,\"portPolicy\":\"static\",\"template\":{\"spec\":{\"containers\":[{\"image\":\"gcr.io/agones-images/udp-server:0.8\",\"name\":\"simple-udp\"}]}}}}\n" + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"stable.agones.dev/v1alpha1\",\"kind\":\"GameServer\",\"metadata\":{\"annotations\":{},\"name\":\"simple-udp\",\"namespace\":\"default\"},\"spec\":{\"containerPort\":7654,\"hostPort\":7777,\"portPolicy\":\"static\",\"template\":{\"spec\":{\"containers\":[{\"image\":\"gcr.io/agones-images/udp-server:0.9\",\"name\":\"simple-udp\"}]}}}}\n" }, "clusterName": "", "creationTimestamp": "2018-03-02T21:41:05Z", @@ -210,7 +210,7 @@ $ curl http://localhost:8001/apis/stable.agones.dev/v1alpha1/namespaces/default/ "spec": { "containers": [ { - "image": "gcr.io/agones-images/udp-server:0.8", + "image": "gcr.io/agones-images/udp-server:0.9", "name": "simple-udp", "resources": {} } @@ -317,7 +317,7 @@ $ curl -d '{"apiVersion":"stable.agones.dev/v1alpha1","kind":"FleetAllocation"," "spec": { "containers": [ { - "image": "gcr.io/agones-images/udp-server:0.8", + "image": "gcr.io/agones-images/udp-server:0.9", "name": "simple-udp", "resources": {} } diff --git a/site/content/en/docs/Installation/helm.md b/site/content/en/docs/Installation/helm.md index 6750d54d87..04bdba091b 100644 --- a/site/content/en/docs/Installation/helm.md +++ b/site/content/en/docs/Installation/helm.md @@ -158,6 +158,13 @@ The following tables lists the configurable parameters of the Agones chart and t | `agones.ping.nodeSelector` | Ping [node labels][nodeSelector] for pod assignment | `{}` | | `agones.ping.tolerations` | Ping [toleration][toleration] labels for pod assignment | `[]` | | `agones.ping.affinity` | Ping [affinity][affinity] settings for pod assignment | `{}` | +| `agones.allocator.install` | Whether to install the [allocator service][allocator] | `true` | +| `agones.allocator.replicas` | The number of replicas to run in the deployment | `3` | +| `agones.allocator.http.expose` | Expose the http allocator service via a Service | `true` | +| `agones.allocator.http.response` | The string response returned from the http service | `ok` | +| `agones.allocator.http.port` | The port to expose on the service | `443` | +| `agones.allocator.http.serviceType` | The [Service Type][service] of the HTTP Service | `LoadBalancer` | +| `agones.allocator.generateTLS` | Set to true to generate TLS certificates or false to provide certificates in `certs/allocator/*`| `true` | | `gameservers.namespaces` | a list of namespaces you are planning to use to deploy game servers | `["default"]` | | `gameservers.minPort` | Minimum port to use for dynamic port allocation | `7000` | | `gameservers.maxPort` | Maximum port to use for dynamic port allocation | `8000` | diff --git a/site/content/en/docs/Installation/terraform.md b/site/content/en/docs/Installation/terraform.md index 3cec872316..ed9be19bbd 100644 --- a/site/content/en/docs/Installation/terraform.md +++ b/site/content/en/docs/Installation/terraform.md @@ -1,5 +1,5 @@ --- -title: "Deploy GKE cluster and install Agones using Terraform" +title: "Deploy GKE/AKS cluster and install Agones using Terraform" linkTitle: "Install with Terraform" weight: 4 description: > @@ -11,14 +11,17 @@ description: > - Terraform v0.11.13 - [Helm](https://docs.helm.sh/helm/) package manager 2.10.0+ -- Access to Google Cloud Kubernetes Engine -- `gcloud` utility installed +- Access to the the Kubernetes hosting provider you are using (e.g. `gcloud` or `az` utility installed) - Git -## Installing the Agones as Terraform submodule +# Installing the Agones as Terraform submodule on Google Kubernetes Engine You can use Terraform to provision your GKE cluster and install agones on it using Helm Terraform provider. +First step would be to enable `Kubernetes Engine API`. From the Cloud Console, navigate to APIs & Services > Dashboard, then click `Enable APIs and Services`. Type `kubernetes` in the search box, and you should find the Kubernetes Engine API. Click Enable. + +Install `gcloud` utility by following [these instructions](https://cloud.google.com/sdk/install). + GKE cluster would contain 3 Node Pools: - Primary Node Pool with `"game-server"` tag, containing 4 nodes. - `"agones-system"` node pool for Agones Controller. @@ -31,7 +34,7 @@ By default you will receive the latest version from [Helm repository](https://ag ## Example and parameters which is configurable The example of submodule configuration could be found here: - {{< ghlink href="examples/terraform-submodule/module.tf" >}}Terraform configuration with Agones submodule{{< /ghlink >}} + {{< ghlink href="examples/terraform-submodules/gke/module.tf" >}}Terraform configuration with Agones submodule{{< /ghlink >}} Configurable parameters and their meaning: - password - if not specified basic Auth would be disabled in GKE cluster @@ -70,7 +73,7 @@ Fetching cluster endpoint and auth data. kubeconfig entry generated for test-cluster. ``` -Check that your has access to kubernetes cluster: +Check that you have access to kubernetes cluster: ``` kubectl get nodes ``` @@ -83,3 +86,49 @@ Run next command to delete all Terraform provisioned resources: ``` terraform destroy ``` + +# Installing the Agones as Terraform submodule on Azure Kubernetes Service + +You can deploy Kubernetes cluster on Azure Kubernetes Service and install Agones using terraform. + +Install `az` utility by following [these instructions](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest). + +The example of AKS submodule configuration could be found here: + {{< ghlink href="examples/terraform-submodules/aks/module.tf" >}}Terraform configuration with Agones submodule{{< /ghlink >}} + +Copy `module.tf` file into a separate folder. + +Login to Azure CLI: +``` +az login +``` + +Configure your terraform: +``` +terraform init +``` + +Now you can deploy your cluster (use variables from the above `az ad sp create-for-rbac` command output): +``` +terraform apply -var client_id="" -var client_secret="" +``` + +Once you created all resources on AKS you can get the credentials so that you can use `kubectl` to configure your cluster: +``` +az aks get-credentials --resource-group agonesRG --name agones +``` + +Check that you have access to kubernetes cluster: +``` +kubectl get nodes +``` + +## Uninstall the Agones and delete AKS cluster + +Run next command to delete all Terraform provisioned resources: +``` +terraform destroy +``` + +## Reference +Details on how you can authenticate your AKS terraform provider using official [instructions](https://www.terraform.io/docs/providers/azurerm/auth/service_principal_client_secret.html) \ No newline at end of file diff --git a/test/e2e/allocator_test.go b/test/e2e/allocator_test.go new file mode 100644 index 0000000000..79755d8a27 --- /dev/null +++ b/test/e2e/allocator_test.go @@ -0,0 +1,129 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "agones.dev/agones/pkg/apis/allocation/v1alpha1" + stablev1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + e2e "agones.dev/agones/test/e2e/framework" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestAllocator(t *testing.T) { + t.Parallel() + + kubeCore := framework.KubeClient.CoreV1() + svc, err := kubeCore.Services("agones-system").Get("gameserver-allocator", metav1.GetOptions{}) + if !assert.Nil(t, err) { + return + } + if !assert.NotNil(t, svc.Status.LoadBalancer) { + return + } + if !assert.Equal(t, 1, len(svc.Status.LoadBalancer.Ingress)) { + return + } + if !assert.NotNil(t, 0, svc.Status.LoadBalancer.Ingress[0].IP) { + return + } + + port := svc.Spec.Ports[0] + requestURL := fmt.Sprintf("https://%s:%d/v1alpha1/gameserverallocation", svc.Status.LoadBalancer.Ingress[0].IP, port.Port) + + flt, err := createFleet() + if !assert.Nil(t, err) { + return + } + framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + gsa := &v1alpha1.GameServerAllocation{ + Spec: v1alpha1.GameServerAllocationSpec{ + Required: metav1.LabelSelector{MatchLabels: map[string]string{stablev1alpha1.FleetNameLabel: flt.ObjectMeta.Name}}, + }} + + body, err := json.Marshal(gsa) + if !assert.Nil(t, err) { + return + } + + client, err := creatRestClient("agones-system", "allocator-tls") + if !assert.Nil(t, err) { + return + } + response, err := client.Post(requestURL, "application/json", bytes.NewBuffer(body)) + if !assert.Nil(t, err) { + return + } + defer response.Body.Close() // nolint: errcheck + + assert.Equal(t, http.StatusOK, response.StatusCode) + body, err = ioutil.ReadAll(response.Body) + if !assert.Nil(t, err) { + return + } + result := v1alpha1.GameServerAllocation{} + err = json.Unmarshal(body, &result) + if !assert.Nil(t, err) { + return + } + assert.Equal(t, v1alpha1.GameServerAllocationAllocated, result.Status.State) +} + +// creatRestClient creates a rest client with proper certs to make a remote call. +func creatRestClient(namespace, clientSecretName string) (*http.Client, error) { + kubeCore := framework.KubeClient.CoreV1() + clientSecret, err := kubeCore.Secrets(namespace).Get(clientSecretName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + // Create http client using cert + clientCert := clientSecret.Data["tls.crt"] + clientKey := clientSecret.Data["tls.key"] + if clientCert == nil || clientKey == nil { + return nil, fmt.Errorf("missing certificate") + } + + // Load client cert + cert, err := tls.X509KeyPair(clientCert, clientKey) + if err != nil { + return nil, err + } + // Setup HTTPS client + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &cert, nil + }, + }, + }, + }, nil +} + +func createFleet() (*stablev1alpha1.Fleet, error) { + fleets := framework.AgonesClient.StableV1alpha1().Fleets(defaultNs) + fleet := defaultFleet() + return fleets.Create(fleet) +} diff --git a/test/e2e/fleet_test.go b/test/e2e/fleet_test.go index 135243457d..d31716edf9 100644 --- a/test/e2e/fleet_test.go +++ b/test/e2e/fleet_test.go @@ -23,6 +23,7 @@ import ( "agones.dev/agones/pkg/apis" allocationv1alpha1 "agones.dev/agones/pkg/apis/allocation/v1alpha1" "agones.dev/agones/pkg/apis/stable/v1alpha1" + stablev1alpha1 "agones.dev/agones/pkg/client/clientset/versioned/typed/stable/v1alpha1" e2e "agones.dev/agones/test/e2e/framework" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -802,50 +803,95 @@ func TestUpdateFleetScheduling(t *testing.T) { }) } -// TestFleetRecreateGameServerOnPodDeletion ensure that if a pod from a GameServer -// is deleted, the GameServer is deleted and replaced. -func TestFleetRecreateGameServerOnPodDeletion(t *testing.T) { +// TestFleetRecreateGameServers tests various gameserver shutdown scenarios to ensure +// that recreation happens as expected +func TestFleetRecreateGameServers(t *testing.T) { t.Parallel() - alpha1 := framework.AgonesClient.StableV1alpha1() - flt := defaultFleet() - flt.Spec.Replicas = 1 + tests := map[string]struct { + f func(t *testing.T, list *v1alpha1.GameServerList) + }{ + "pod deletion": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + podClient := framework.KubeClient.CoreV1().Pods(defaultNs) - flt, err := alpha1.Fleets(defaultNs).Create(flt) - podClient := framework.KubeClient.CoreV1().Pods(defaultNs) + for _, gs := range list.Items { + pod, err := podClient.Get(gs.ObjectMeta.Name, metav1.GetOptions{}) + assert.NoError(t, err) - if assert.Nil(t, err) { - defer alpha1.Fleets(defaultNs).Delete(flt.ObjectMeta.Name, nil) // nolint:errcheck + assert.True(t, metav1.IsControlledBy(pod, &gs)) + + err = podClient.Delete(pod.ObjectMeta.Name, nil) + assert.NoError(t, err) + } + }}, + "gameserver shutdown": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + for _, gs := range list.Items { + var reply string + reply, err := e2e.SendGameServerUDP(&gs, "EXIT") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: EXIT\n", reply) + } + }}, + "gameserver unhealthy": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + for _, gs := range list.Items { + var reply string + reply, err := e2e.SendGameServerUDP(&gs, "UNHEALTHY") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: UNHEALTHY\n", reply) + } + }}, } - framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + for k, v := range tests { + t.Run(k, func(t *testing.T) { + alpha1 := framework.AgonesClient.StableV1alpha1() + flt := defaultFleet() + // add more game servers, to hunt for race conditions + flt.Spec.Replicas = 10 - selector := labels.SelectorFromSet(labels.Set{v1alpha1.FleetNameLabel: flt.ObjectMeta.Name}) - list, err := alpha1.GameServers(defaultNs).List(metav1.ListOptions{LabelSelector: selector.String()}) - assert.NoError(t, err) + flt, err := alpha1.Fleets(defaultNs).Create(flt) + if assert.Nil(t, err) { + defer alpha1.Fleets(defaultNs).Delete(flt.ObjectMeta.Name, nil) // nolint:errcheck + } - assert.Len(t, list.Items, 1) - gs := list.Items[0] - pod, err := podClient.Get(gs.ObjectMeta.Name, metav1.GetOptions{}) - assert.NoError(t, err) + framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) - assert.True(t, metav1.IsControlledBy(pod, &gs)) + list, err := listGameServers(flt, alpha1) + assert.NoError(t, err) + assert.Len(t, list.Items, int(flt.Spec.Replicas)) - err = podClient.Delete(pod.ObjectMeta.Name, nil) - assert.NoError(t, err) + // apply deletion function + logrus.Info("applying deletion function") + v.f(t, list) - err = wait.Poll(time.Second, time.Minute, func() (done bool, err error) { - _, err = alpha1.GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{}) + for i, gs := range list.Items { + err = wait.Poll(time.Second, time.Minute, func() (done bool, err error) { + _, err = alpha1.GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{}) - if err != nil && k8serrors.IsNotFound(err) { - return true, nil - } + if err != nil && k8serrors.IsNotFound(err) { + logrus.Infof("gameserver %d/%d not found", i+1, flt.Spec.Replicas) + return true, nil + } - return false, err - }) - assert.NoError(t, err) + return false, err + }) + assert.NoError(t, err) + } - framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + }) + } +} + +func listGameServers(flt *v1alpha1.Fleet, getter stablev1alpha1.GameServersGetter) (*v1alpha1.GameServerList, error) { + selector := labels.SelectorFromSet(labels.Set{v1alpha1.FleetNameLabel: flt.ObjectMeta.Name}) + return getter.GameServers(defaultNs).List(metav1.ListOptions{LabelSelector: selector.String()}) } // Counts the number of gameservers with the specified scheduling strategy in a fleet diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 3046a5c05f..0930748321 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -94,6 +94,8 @@ func (f *Framework) CreateGameServerAndWaitUntilReady(ns string, gs *v1alpha1.Ga return nil, fmt.Errorf("Ready GameServer instance has no port: %v", readyGs.Status) } + logrus.WithField("name", newGs.ObjectMeta.Name).Info("GameServer Ready") + return readyGs, nil } diff --git a/test/e2e/gameserver_test.go b/test/e2e/gameserver_test.go index e061652523..0ce47bbb72 100644 --- a/test/e2e/gameserver_test.go +++ b/test/e2e/gameserver_test.go @@ -175,14 +175,10 @@ func TestUnhealthyGameServersWithoutFreePorts(t *testing.T) { } newGs, err := gameServers.Create(gs.DeepCopy()) - assert.Nil(t, err) - - _, err = framework.WaitForGameServerState(newGs, v1alpha1.GameServerStateUnhealthy, 10*time.Second) - assert.NotNil(t, err) + assert.NoError(t, err) - _, err = gameServers.Get(newGs.Name, metav1.GetOptions{}) - assert.NotNil(t, err) - assert.True(t, k8serrors.IsNotFound(err)) + _, err = framework.WaitForGameServerState(newGs, v1alpha1.GameServerStateUnhealthy, time.Minute) + assert.NoError(t, err) } func TestGameServerUnhealthyAfterDeletingPod(t *testing.T) { @@ -208,27 +204,7 @@ func TestGameServerUnhealthyAfterDeletingPod(t *testing.T) { err = podClient.Delete(pod.ObjectMeta.Name, nil) assert.NoError(t, err) - // TODO [markmandel@google.com]: Should GameServers that are Unhealthy and not in a fleet be deleted? - err = wait.PollImmediate(2*time.Second, time.Minute, func() (bool, error) { - gs, err := framework.AgonesClient.StableV1alpha1().GameServers(readyGs.Namespace).Get(readyGs.ObjectMeta.Name, metav1.GetOptions{}) - - // just in case - if k8serrors.IsNotFound(err) { - return true, nil - } - - if err != nil { - logrus.WithError(err).Warn("error retrieving gameserver") - return false, nil - } - - if gs.Status.State == v1alpha1.GameServerStateUnhealthy { - return true, nil - } - - return false, nil - }) - + _, err = framework.WaitForGameServerState(readyGs, v1alpha1.GameServerStateUnhealthy, time.Minute) assert.NoError(t, err) } @@ -314,6 +290,35 @@ func TestGameServerSelfAllocate(t *testing.T) { assert.NoError(t, err) } +func TestGameServerShutdown(t *testing.T) { + t.Parallel() + gs := defaultGameServer() + readyGs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs) + if err != nil { + t.Fatalf("Could not get a GameServer ready: %v", err) + } + assert.Equal(t, readyGs.Status.State, v1alpha1.GameServerStateReady) + + reply, err := e2eframework.SendGameServerUDP(readyGs, "EXIT") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: EXIT\n", reply) + + err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) { + gs, err = framework.AgonesClient.StableV1alpha1().GameServers(defaultNs).Get(readyGs.ObjectMeta.Name, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return true, nil + } + + return false, err + }) + + assert.NoError(t, err) +} + func defaultGameServer() *v1alpha1.GameServer { gs := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "udp-server", Namespace: defaultNs}, Spec: v1alpha1.GameServerSpec{ diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 39c88a057b..76a7cea91a 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -34,8 +34,8 @@ func TestMain(m *testing.M) { usr, _ := user.Current() kubeconfig := flag.String("kubeconfig", filepath.Join(usr.HomeDir, "/.kube/config"), "kube config path, e.g. $HOME/.kube/config") - gsimage := flag.String("gameserver-image", "gcr.io/agones-images/udp-server:0.8", - "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.8") + gsimage := flag.String("gameserver-image", "gcr.io/agones-images/udp-server:0.9", + "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.9") pullSecret := flag.String("pullsecret", "", "optional secret to be used for pulling the gameserver and/or Agones SDK sidecar images") stressTestLevel := flag.Int("stress", 0, "enable stress test at given level 0-100")