mirror of
https://github.com/googleforgames/open-match.git
synced 2025-03-22 19:08:31 +00:00
Compare commits
5 Commits
release-1.
...
v0.5.0
Author | SHA1 | Date | |
---|---|---|---|
6a5d516463 | |||
a9943cc9c4 | |||
50db76d1ff | |||
50b52dc2e9 | |||
82226b1be1 |
@ -1,24 +1,7 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
.git
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.nupkg
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
@ -48,8 +31,6 @@ detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
@ -80,9 +61,6 @@ bld/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
@ -105,33 +83,14 @@ install/yaml/
|
||||
# Temp Directories
|
||||
tmp/
|
||||
|
||||
# Terraform context
|
||||
.terraform
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
|
||||
# Credential Files
|
||||
creds.json
|
||||
|
||||
# Open Match Binaries
|
||||
cmd/backend/backend
|
||||
cmd/frontend/frontend
|
||||
cmd/query/query
|
||||
cmd/synchronizer/synchronizer
|
||||
# Compiled Binaries
|
||||
cmd/minimatch/minimatch
|
||||
cmd/swaggerui/swaggerui
|
||||
tools/certgen/certgen
|
||||
examples/demo/demo
|
||||
examples/functions/golang/soloduel/soloduel
|
||||
test/evaluator/evaluator
|
||||
test/matchfunction/matchfunction
|
||||
tools/reaper/reaper
|
||||
|
||||
# Open Match Build Directory
|
||||
cmd/backendapi/backendapi
|
||||
cmd/frontendapi/frontendapi
|
||||
cmd/mmlogicapi/mmlogicapi
|
||||
examples/backendclient/backendclient
|
||||
examples/evaluators/golang/serving/serving
|
||||
examples/functions/golang/grpc-serving/grpc-serving
|
||||
test/cmd/clientloadgen/clientloadgen
|
||||
test/cmd/frontendclient/frontendclient
|
||||
build/
|
||||
|
||||
# Secrets Directories
|
||||
install/helm/open-match/secrets/
|
||||
|
||||
# Helm tar charts
|
||||
install/helm/open-match/charts/
|
||||
|
@ -1,17 +1,3 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This file specifies files that are *not* uploaded to Google Cloud Platform
|
||||
# using gcloud. It follows the same syntax as .gitignore, with the addition of
|
||||
# "#!include" directives (which insert the entries of the given .gitignore-style
|
||||
|
25
.github/ISSUE_TEMPLATE/apichange.md
vendored
25
.github/ISSUE_TEMPLATE/apichange.md
vendored
@ -1,25 +0,0 @@
|
||||
---
|
||||
name: Breaking API change
|
||||
about: Details of a breaking API change proposal.
|
||||
title: 'API change: <>'
|
||||
labels: breaking api change
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
<High level description of this change>
|
||||
|
||||
## Motivation
|
||||
|
||||
<What is the primary motivation for this API change>
|
||||
|
||||
## Impact
|
||||
|
||||
<What usage does this impact? Add details here such that a consumer of Open
|
||||
Match API can clearly tell if this will impact them>
|
||||
|
||||
## Change Proto
|
||||
|
||||
<Add snippet of the proposed change proto>
|
||||
|
30
.github/ISSUE_TEMPLATE/bugreport.md
vendored
30
.github/ISSUE_TEMPLATE/bugreport.md
vendored
@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: kind/bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
|
||||
|
||||
If the matter is security related, please disclose it privately via
|
||||
-->
|
||||
|
||||
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**How to reproduce it (as minimally and precisely as possible)**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Output of `kubectl version`**:
|
||||
|
||||
**Cloud Provider/Platform (AKS, GKE, Minikube etc.)**:
|
||||
|
||||
**Open Match Release Version**:
|
||||
|
||||
**Install Method(yaml/helm):**:
|
20
.github/ISSUE_TEMPLATE/featurerequest.md
vendored
20
.github/ISSUE_TEMPLATE/featurerequest.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: kind/feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
181
.github/ISSUE_TEMPLATE/release.md
vendored
181
.github/ISSUE_TEMPLATE/release.md
vendored
@ -1,181 +0,0 @@
|
||||
---
|
||||
name: Publish a Release
|
||||
about: Instructions and checklist for creating a release.
|
||||
title: 'Release X.Y.Z-rc.N'
|
||||
labels: kind/release
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
# Open Match Release Process
|
||||
|
||||
Follow these instructions to create an Open Match release. The output of the
|
||||
release process is new images and new configuration.
|
||||
|
||||
## Getting setup
|
||||
|
||||
**NOTE: The instructions below are NOT strictly copy-pastable and assume 0.5**
|
||||
**release. Please update the version number for your commands.**
|
||||
|
||||
The Git flow for pushing a new release is similar to the development process
|
||||
but there are some small differences.
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```shell
|
||||
# Clone your fork of the Open Match repository.
|
||||
git clone git@github.com:afeddersen/open-match.git
|
||||
# Change directory to the git repository.
|
||||
cd open-match
|
||||
# Add a remote, you'll be pushing to this.
|
||||
git remote add upstream https://github.com/googleforgames/open-match.git
|
||||
```
|
||||
|
||||
### 2. Release Branch
|
||||
|
||||
If you're creating the first release of the version, that would be `0.5.0-rc.1`
|
||||
then you'll need to create the release branch.
|
||||
|
||||
```shell
|
||||
# Create a local release branch.
|
||||
git checkout -b release-0.5 upstream/master
|
||||
# Push the branch upstream.
|
||||
git push upstream release-0.5
|
||||
```
|
||||
|
||||
otherwise there should already be a `release-0.5` branch so run,
|
||||
|
||||
```shell
|
||||
# Checkout the release branch.
|
||||
git checkout -b release-0.5 upstream/release-0.5
|
||||
```
|
||||
|
||||
**NOTE: The branch name must be in the format, `release-X.Y` otherwise**
|
||||
**some artifacts will not be pushed.**
|
||||
|
||||
## Releases & Versions
|
||||
|
||||
Open Match uses Semantic Versioning 2.0.0. If you're not familiar please
|
||||
see the documentation - [https://semver.org/](https://semver.org/).
|
||||
|
||||
Full Release / Stable Release:
|
||||
|
||||
* The final software product. Stable, reliable, etc...
|
||||
* Example: 1.0.0, 1.1.0
|
||||
|
||||
Release Candidate (RC):
|
||||
|
||||
* A release candidate (RC) is a version with the potential to be the final
|
||||
product but it hasn't validated by automated and/or manual tests.
|
||||
* Example: 1.0.0-rc.1
|
||||
|
||||
Hot Fixes:
|
||||
|
||||
* Code developed to correct a major software bug or fault
|
||||
that's been discovered after the full release.
|
||||
* Example: 1.0.1
|
||||
|
||||
Preview:
|
||||
|
||||
* Rare, a one off release cut from the master branch to provide early access
|
||||
to APIs or some other major change.
|
||||
* **NOTE: There's no branch for this release.**
|
||||
* Example: 0.5-preview.1
|
||||
|
||||
**NOTE: Semantic versioning is enforced by `go mod`. A non-compliant version**
|
||||
**tag will cause `go get` to break for users.**
|
||||
|
||||
# Detailed Instructions
|
||||
|
||||
## Find and replace
|
||||
|
||||
Below this point you will see {version} used as a placeholder for future
|
||||
releases. Find {version} and replace with the current release (e.g. 0.5.0)
|
||||
|
||||
## Create a release branch in the upstream open-match repository
|
||||
|
||||
**Note: This step is performed by the person who starts the release. It is
|
||||
only required once.**
|
||||
|
||||
- [ ] Create the branch in the **upstream** repository. It should be named
|
||||
release-X.Y. Example: release-0.5. At this point there's effectively a code
|
||||
freeze for this version and all work on master will be included in a future
|
||||
version. If you're on the branch that you created in the *getting setup*
|
||||
section above you should be able to push upstream.
|
||||
|
||||
```shell
|
||||
git push origin release-0.5
|
||||
```
|
||||
|
||||
- [ ] Announce a PR freeze on release-X.Y branch on [open-match-discuss@](mailing-list-post).
|
||||
- [ ] Open the [`Makefile`](makefile-version) and change BASE_VERSION entry.
|
||||
- [ ] Open the [`install/helm/open-match/Chart.yaml`](om-chart-yaml-version) and change the `appVersion` and `version` entries.
|
||||
- [ ] Open the [`install/helm/open-match/values.yaml`](om-values-yaml-version) and change the `tag` entries.
|
||||
- [ ] Open the [`cloudbuild.yaml`] and change the `_OM_VERSION` entry.
|
||||
- [ ] There might be additional references to the old version but be careful not to change it for places that have it for historical purposes.
|
||||
- [ ] Run `make release`
|
||||
- [ ] Run `make api/api.md` in open-match repo to update the auto-generated API references in open-match-docs repo.
|
||||
- [ ] Use the files under the `build/release/` directory for the Open Match installation guide. Make sure the artifacts work as expected - these are the artifacts that will be published to the GCS bucket and used in our release assets.
|
||||
- [ ] Create a PR with the changes, include the release candidate name, and point it to the release branch.
|
||||
- [ ] Go to [open-match-build](https://pantheon.corp.google.com/cloud-build/triggers?project=open-match-build) and update all *post submit* triggers' `_GCB_LATEST_VERSION` value to the `X.Y` of the release. This value should only increase as it's used to determine the latest stable version.
|
||||
- [ ] Merge your changes once the PR is approved.
|
||||
|
||||
## Create a release branch in the upstream open-match-docs repository
|
||||
- [ ] Open [`Makefile`](makefile-version) and change BASE_VERSION entry.
|
||||
- [ ] Open [`cloudbuild.yaml`] and change the `_OM_VERSION` entry.
|
||||
- [ ] Open [`site/config.toml`] and change the `release_version` entry.
|
||||
- [ ] Open [`site/static/swaggerui/config.json`] and change the `api/VERSION/...` entries
|
||||
- [ ] Create a PR with the changes, include the release candidate name, and point it to the release branch.
|
||||
|
||||
## Complete Milestone
|
||||
|
||||
**Note: This step is performed by the person who starts the release. It is
|
||||
only required once.**
|
||||
- [ ] Create the next [version milestone](https://github.com/googleforgames/open-match/milestones) and use [semantic versioning](https://semver.org/) when naming it to be consistent with the [Go community](https://blog.golang.org/versioning-proposal).
|
||||
- [ ] Create a *draft* [release](https://github.com/googleforgames/open-match/releases). Note that github has both "Pre-release" and "draft" as different concepts for a release. Until the release is finalized, only use "Save draft", and do not use "Publish release".
|
||||
- [ ] Use the [release template](https://github.com/googleforgames/open-match/blob/master/docs/governance/templates/release.md)
|
||||
- [ ] `Tag` = v{version}. Example: v0.5.0. Append -rc.# for release candidates. Example: v0.5.0-rc.1.
|
||||
- [ ] `Target` = release-X.Y. Example: release-0.5.
|
||||
- [ ] `Release Title` = `Tag`
|
||||
- [ ] `Write` section will contain the contents from the [release template](https://github.com/googleforgames/open-match/blob/master/docs/governance/templates/release.md).
|
||||
- [ ] Add the milestone to all PRs and issues that were merged since the last milestone. Look at the [releases page](https://github.com/googleforgames/open-match/releases) and look for the "X commits to master since this release" for the diff.
|
||||
- [ ] Review all [milestone-less closed issues](https://github.com/googleforgames/open-match/issues?q=is%3Aissue+is%3Aclosed+no%3Amilestone) and assign the appropriate milestone.
|
||||
- [ ] Review all [issues in milestone](https://github.com/googleforgames/open-match/milestones) for proper [labels](https://github.com/googleforgames/open-match/labels) (ex: area/build).
|
||||
- [ ] Review all [milestone-less closed PRs](https://github.com/googleforgames/open-match/pulls?q=is%3Apr+is%3Aclosed+no%3Amilestone) and assign the appropriate milestone.
|
||||
- [ ] Review all [PRs in milestone](https://github.com/googleforgames/open-match/milestones) for proper [labels](https://github.com/googleforgames/open-match/labels) (ex: area/build).
|
||||
- [ ] View all open entries in milestone and move them to a future milestone if they aren't getting closed in time. https://github.com/googleforgames/open-match/milestones/v{version}
|
||||
- [ ] Review all closed PRs against the milestone. Put the user visible changes into the release notes using the suggested format. https://github.com/googleforgames/open-match/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+is%3Amerged+milestone%3Av{version}
|
||||
- [ ] Review all closed issues against the milestone. Put the user visible changes into the release notes using the suggested format. https://github.com/googleforgames/open-match/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Av{version}
|
||||
- [ ] Verify the [milestone](https://github.com/googleforgames/open-match/milestones) is effectively 100% at this point with the exception of the release issue itself.
|
||||
|
||||
## Build Artifacts
|
||||
|
||||
- [ ] Go to the History section and find the "Post Submit" build of the merged commit that's running. Wait for it to go Green. If it's red, fix error repeat this section. Take note of the docker image version tag for next step. Example: 0.5.0-a4706cb.
|
||||
- [ ] Run `./docs/governance/templates/release.sh {source version tag} {version}` to copy the images to open-match-public-images.
|
||||
- [ ] If this is a new minor version in the newest major version then run `./docs/governance/templates/release.sh {source version tag} latest`.
|
||||
- [ ] Copy the files from `build/release/` generated from `make release` to the release draft you created. You can drag and drop the files using the Github UI.
|
||||
- [ ] Update [Slack invitation link](https://slack.com/help/articles/201330256-invite-new-members-to-your-workspace#share-an-invite-link) in [open-match.dev](https://open-match.dev/site/docs/contribute/#get-involved).
|
||||
- [ ] Test Open Match installation under GKE and Minikube enviroment using YAML files and Helm. Follow the [First Match](https://development.open-match.dev/site/docs/getting-started/first_match/) guide, run `make proxy-demo`, and open `localhost:51507` to make sure everything works.
|
||||
- [ ] Minikube: Run `make create-mini-cluster` to create a local cluster with latest Kubernetes API version.
|
||||
- [ ] GKE: Run `make create-gke-cluster` to create a GKE cluster.
|
||||
- [ ] Helm: Run `helm install open-match -n open-match open-match/open-match`
|
||||
- [ ] Update usage requirements in the Installation doc - e.g. supported minikube version, kubectl version, golang version, etc.
|
||||
|
||||
## Finalize
|
||||
|
||||
- [ ] Save the release as a draft.
|
||||
- [ ] Circulate the draft release to active contributors. Where reasonable, get everyone's ok on the release notes before continuing.
|
||||
- [ ] Publish the [Release](om-release) in Github. This will notify repository watchers.
|
||||
|
||||
## Announce
|
||||
|
||||
- [ ] Send an email to the [mailing list](mailing-list-post) with the release details (copy-paste the release blog post)
|
||||
- [ ] Send a chat on the [Slack channel](om-slack). "Open Match {version} has been released! Check it out at {release url}."
|
||||
|
||||
[om-slack]: https://open-match.slack.com/
|
||||
[mailing-list-post]: https://groups.google.com/forum/#!newtopic/open-match-discuss
|
||||
[release-template]: https://github.com/googleforgames/open-match/blob/master/docs/governance/templates/release.md
|
||||
[makefile-version]: https://github.com/googleforgames/open-match/blob/master/Makefile#L53
|
||||
[om-chart-yaml-version]: https://github.com/googleforgames/open-match/blob/master/install/helm/open-match/Chart.yaml#L16
|
||||
[om-values-yaml-version]: https://github.com/googleforgames/open-match/blob/master/install/helm/open-match/values.yaml#L16
|
||||
[om-release]: https://github.com/googleforgames/open-match/releases/new
|
||||
[readme-deploy]: https://github.com/googleforgames/open-match/blob/master/README.md#deploy-to-kubernetes
|
16
.github/pull_request_template.md
vendored
16
.github/pull_request_template.md
vendored
@ -1,16 +0,0 @@
|
||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||
If this is your first time, please read our contributor guidelines: https://github.com/googleforgames/open-match/blob/master/CONTRIBUTING.md and developer guide https://github.com/googleforgames/open-match/blob/master/docs/development.md
|
||||
-->
|
||||
|
||||
**What this PR does / Why we need it**:
|
||||
|
||||
**Which issue(s) this PR fixes**:
|
||||
<!--
|
||||
*Automatically closes linked issue when PR is merged.
|
||||
Usage: `Closes #<issue number>`, or `Closes (paste link of issue)`.
|
||||
-->
|
||||
Closes #
|
||||
|
||||
**Special notes for your reviewer**:
|
||||
|
||||
|
53
.gitignore
vendored
53
.gitignore
vendored
@ -1,22 +1,7 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.nupkg
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
@ -46,8 +31,6 @@ detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
@ -78,8 +61,6 @@ bld/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
@ -102,29 +83,13 @@ install/yaml/
|
||||
# Temp Directories
|
||||
tmp/
|
||||
|
||||
# Terraform context
|
||||
.terraform
|
||||
*.tfstate.backup
|
||||
|
||||
# Credential Files
|
||||
creds.json
|
||||
|
||||
# Open Match Binaries
|
||||
cmd/backend/backend
|
||||
cmd/frontend/frontend
|
||||
cmd/query/query
|
||||
cmd/synchronizer/synchronizer
|
||||
# Compiled Binaries
|
||||
cmd/minimatch/minimatch
|
||||
cmd/swaggerui/swaggerui
|
||||
tools/certgen/certgen
|
||||
examples/demo/demo
|
||||
examples/functions/golang/soloduel/soloduel
|
||||
test/evaluator/evaluator
|
||||
test/matchfunction/matchfunction
|
||||
tools/reaper/reaper
|
||||
|
||||
# Secrets Directories
|
||||
install/helm/open-match/secrets/
|
||||
|
||||
# Helm tar charts
|
||||
install/helm/open-match/charts/
|
||||
cmd/backendapi/backendapi
|
||||
cmd/frontendapi/frontendapi
|
||||
cmd/mmlogicapi/mmlogicapi
|
||||
examples/backendclient/backendclient
|
||||
examples/evaluators/golang/serving/serving
|
||||
examples/functions/golang/grpc-serving/grpc-serving
|
||||
test/cmd/clientloadgen/clientloadgen
|
||||
test/cmd/frontendclient/frontendclient
|
||||
|
225
.golangci.yaml
225
.golangci.yaml
@ -1,225 +0,0 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# https://github.com/golangci/golangci-lint#config-file
|
||||
service:
|
||||
golangci-lint-version: 1.18.0
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 5m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
build-tags:
|
||||
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs:
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files: '.*\.gw\.go'
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
|
||||
format: colored-line-number
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
|
||||
# print linter name in the end of issue text, default is true
|
||||
print-linter-name: true
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: true
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: true
|
||||
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
|
||||
# settings per analyzer
|
||||
settings:
|
||||
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
golint:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 10
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 3
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
include-go-root: false
|
||||
packages:
|
||||
- github.com/davecgh/go-spew/spew
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- someword
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 120
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# True by default.
|
||||
simple: true
|
||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
gocritic:
|
||||
# Which checks should be enabled; can't be combined with 'disabled-checks';
|
||||
# See https://go-critic.github.io/overview#checks-overview
|
||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||
# By default list of stable checks is used.
|
||||
# enabled-checks:
|
||||
# - rangeValCopy
|
||||
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- performance
|
||||
|
||||
settings: # settings passed to gocritic
|
||||
captLocal: # must be valid enabled check name
|
||||
paramsOnly: true
|
||||
rangeValCopy:
|
||||
sizeThreshold: 32
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- dupl
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gosec
|
||||
- interfacer # deprecated - "A tool that suggests interfaces is prone to bad suggestions"
|
||||
- lll
|
||||
|
||||
#linters:
|
||||
# enable-all: true
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
- abcdef
|
||||
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on test files
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- bodyclose
|
||||
|
||||
# Exclude known linters from partially hard-vendored code,
|
||||
# which is impossible to exclude via "nolint" comments.
|
||||
- path: internal/hmac/
|
||||
text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# Exclude some staticcheck messages
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
|
||||
# Exclude lll issues for long lines with go:generate
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: false
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
60
CHANGELOG.md
Normal file
60
CHANGELOG.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Release history
|
||||
|
||||
## v0.4.0 (alpha)
|
||||
|
||||
### Release notes
|
||||
- Thanks to completion of Issues [#42](issues/42) and [#45](issues/45), there is no longer a need to use the `openmatch-base` image when building components of Open Match. Each stand alone appliation now is self-contained in its `Dockerfile` and `cloudbuild.yaml` files, and builds have been substantially simplified. **Note**: The default `Dockerfile` and `cloudbuild.yaml` now tag their images with the version number, not `dev`, and the YAML files in the `install` directory now reflect this.
|
||||
- This paves the way for CI/CD in an upcoming version.
|
||||
- This paves the way for public images in an upcoming version!
|
||||
|
||||
## v0.3.0 (alpha)
|
||||
This update is focused on the Frontend API and Player Records, including more robust code for indexing, deindexing, reading, writing, and expiring player requests from Open Match state storage. All Frontend API function argument have changed, although many only slightly. Please join the [Slack channel](https://open-match.slack.com/) if you need help ([Signup link](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU))!
|
||||
|
||||
### Release notes
|
||||
- The Frontend API calls have all be changed to reflect the fact that they operate on Players in state storage. To queue a game client, 'CreatePlayer' in Open Match, to get updates 'GetUpdates', and to stop matching, 'DeletePlayer'. The calls are now much more obviously related to how Open Match sees players: they are database records that it creates on demand, updates using MMFs and the Backend API, and deletes when the player is no longer looking for a match.
|
||||
- The Player record in state storage has changed to a more complete hash format, and it no longer makes sense to remove a player's assignment from the Frontend as a separate action to removing their record entirely. `DeleteAssignment()` has therefore been removed. Just use `DeletePlayer` instead; you'll always want the client to re-request matching with its latest attributes anyway.
|
||||
- There is now a module for [indexing and deindexing players in state storage](internal/statestorage/redis/playerindices/playerindices.go). This is a *much* more efficient, as well as being cleaner and more maintainable than the previous implementation which was **hard-coded to index everything** you passed in to the Frontend API at a specific JSON object depth.
|
||||
- This paves the way for dynamically choosing your indicies without restarting the matchmaker. This will be implemented if there is demand. Pull Requests are welcome!
|
||||
- Two internal timestamp-based indices have replaced the previous `timestamp` index. `created` is used to calculate how long a player has been waiting for a match, `accessed` is used to determine when a player needs to be expired out of state storage. Both are prefixed by the string `OM_METADATA` so it should be easy to spot them.
|
||||
- A call to the Frontend API `GetUpdates()` gRPC endpoint returns a stream of player messages. This is used to send updates to state storage for the `Assignment`, `Status`, and `Error` Player fields in near-realtime. **It is the responsibility of the game client to disconnect** from the stream when it has gotten the results it was waiting for!
|
||||
- Moved the rest of the gRPC messages into a shared [`messages.proto` file](api/protobuf-spec/messages.proto).
|
||||
- Added documentation to Frontend API gRPC calls to the [`frontend.proto` file](api/protobuf-spec/frontend.proto).
|
||||
- [Issue #41](https://github.com/GoogleCloudPlatform/open-match/issues/41)|[PR #48](https://github.com/GoogleCloudPlatform/open-match/pull/48) There is now a HA Redis install available in `install/yaml/01-redis-failover.yaml`. This would be used as a drop-in replacement for a single-instance Redis configuration in `install/yaml/01-redis.yaml`. The HA configuration requires that you install the [Redis Operator](https://github.com/spotahome/redis-operator) (note: **currently alpha**, use at your own risk) in your Kubernetes cluster.
|
||||
- As part of this change, the kubernetes service name is now `redis` not `redis-sentinel` to denote that it is accessed using a standard Redis client.
|
||||
- Open Match uses a new feature of the go module [logrus](github.com/sirupsen/logrus) to include filenames and line numbers. If you have an older version in your local build environment, you may need to delete the module and `go get github.com/sirupsen/logrus` again. When building using the provided `cloudbuild.yaml` and `Dockerfile`s this is handled for you.
|
||||
- The program that was formerly in `examples/frontendclient` has been expanded and has been moved to the `test` directory under (`test/cmd/frontendclient/`)[test/cmd/frontendclient/].
|
||||
- The client load generator program has been moved from `test/cmd/client` to (`test/cmd/clientloadgen/`)[test/cmd/clientloadgen/] to better reflect what it does.
|
||||
- [Issue #45](https://github.com/GoogleCloudPlatform/open-match/issues/45) The process for moving the build files (`Dockerfile` and `cloudbuild.yaml`) for each component, example, and test program to their respective directories and out of the repository root has started but won't be completed until a future version.
|
||||
- Put some basic notes in the [production guide](docs/production.md)
|
||||
- Added a basic [roadmap](docs/roadmap.md)
|
||||
|
||||
## v0.2.0 (alpha)
|
||||
This is a pretty large update. Custom MMFs or evaluators from 0.1.0 may need some tweaking to work with this version. Some Backend API function arguments have changed. Please join the [Slack channel](https://open-match.slack.com/) if you need help ([Signup link](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU))!
|
||||
|
||||
v0.2.0 focused on adding additional functionality to Backend API calls and on **reducing the amount of boilerplate code required to make a custom Matchmaking Function**. For this, a new internal API for use by MMFs called the [Matchmaking Logic API (MMLogic API)](README.md#matchmaking-logic-mmlogic-api) has been added. Many of the core components and examples had to be updated to use the new Backend API arguments and the modules to support them, so we recommend you rebuild and redeploy all the components to use v0.2.0.
|
||||
|
||||
### Release notes
|
||||
- MMLogic API is now available. Deploy it to kubernetes using the [appropriate json file]() and check out the [gRPC API specification](api/protobuf-spec/mmlogic.proto) to see how to use it. To write a client against this API, you'll need to compile the protobuf files to your language of choice. There is an associated cloudbuild.yaml file and Dockerfile for it in the root directory.
|
||||
- When using the MMLogic API to filter players into pools, it will attempt to report back the number of players that matched the filters and how long the filters took to query state storage.
|
||||
- An [example MMF](examples/functions/python3/mmlogic-simple/harness.py) using it has been written in Python3. There is an associated cloudbuild.yaml file and Dockerfile for it in the root directory. By default the [example backend client](examples/backendclient/main.go) is now configured to use this MMF, so make sure you have it avaiable before you try to run the latest backend client.
|
||||
- An [example MMF](examples/functions/php/mmlogic-simple/harness.py) using it has been contributed by Ilya Hrankouski in PHP (thanks!). - The API specs have been split into separate files per API and the protobuf messages are in a separate file. Things were renamed slightly as a result, and you will need to update your API clients. The Frontend API hasn't had it's messages moved to the shared messages file yet, but this will happen in an upcoming version.
|
||||
- The [example golang MMF](examples/functions/golang/manual-simple/) has been updated to use the latest data schemas for MatchObjects, and renamed to `manual-simple` to denote that it is manually manipulating Redis, not using the MMLogic API.
|
||||
- The API specs have been split into separate files per API and the protobuf messages are in a separate file. Things were renamed slightly as a result, and you will need to update your API clients. The Frontend API hasn't had it's messages moved to the shared messages file yet, but this will happen in an upcoming version.
|
||||
- The message model for using the Backend API has changed slightly - for calls that make MatchObjects, the expectation is that you will provide a MatchObject with a few fields populated, and it will then be shuttled along through state storage to your MMF and back out again, with various processes 'filling in the blanks' of your MatchObject, which is then returned to your code calling the Backend API. Read the[gRPC API specification](api/protobuf-spec/backend.proto) for more information.
|
||||
- As part of this, compiled protobuf golang modules now live in the [`internal/pb`](internal/pb) directory. There's a handy [bash script](api/protoc-go.sh) for compiling them from the `api/protobuf-spec` directory into this new `internal/pb` directory for development in your local golang environment if you need it.
|
||||
- As part of this Backend API message shift and the advent of the MMLogic API, 'player pools' and 'rosters' are now first-class data structures in MatchObjects for those who wish to use them. You can ignore them if you like, but if you want to use some of the MMLogic API calls to automate tasks for you - things like filtering a pool of players according attributes or adding all the players in your rosters to the ignorelist so other MMFs don't try to grab them - you'll need to put your data into the [protobuf messages](api/protobuf-spec/messages.proto) so Open Match knows how to read them. The sample backend client [test profile JSON](examples/backendclient/profiles/testprofile.json)has been updated to use this format if you want to see an example.
|
||||
- Rosters were formerly space-delimited lists of player IDs. They are now first-class repeated protobuf message fields in the [Roster message format](api/protobuf-spec/messages.proto). That means that in most languages, you can access the roster as a list of players using your native language data structures (more info can be found in the [guide for using protocol buffers in your langauge of choice](https://developers.google.com/protocol-buffers/docs/reference/overview)). If you don't care about the new fields or the new functionality, you can just leave all the other fields but the player ID unset.
|
||||
- Open Match is transitioning to using [protocol buffer messages](https://developers.google.com/protocol-buffers/) as its internal data format. There is now a Redis state storage [golang module](internal/statestorage/redis/redispb/) for marshaling and unmarshaling MatchObject messages to and from Redis. It isn't very clean code right now but will get worked on for the next couple releases.
|
||||
- Ignorelists now exist, and have a Redis state storage [golang module](internal/statestorage/redis/ignorelist/) for CRUD access. Currently three ignorelists are defined in the [config file](config/matchmaker_config.json) with their respective parameters. These are implemented as [Sorted Sets in Redis](https://redis.io/commands#sorted_set).
|
||||
- For those who only want to stand up Open Match and aren't interested in individually tweaking the required kubernetes resources, there are now [three YAML files](install/yaml) that can be used to install Redis, install Open Match, and (optionally) install Prometheus. You'll still need the `sed` [instructions from the Developer Guide](docs/development.md#running-open-match-in-a-development-environment) to substitute in the name of your Docker container registry.
|
||||
- A super-simple module has been created for doing instersections, unions, and differences of lists of player IDs. It lives in `internal/set/set.go`.
|
||||
|
||||
|
||||
### Roadmap
|
||||
- It has become clear from talking to multiple users that the software they write to talk to the Backend API needs a name. 'Backend API Client' is technically correct, but given how many APIs are in Open Match and the overwhelming use of 'Client' to refer to a Game Client in the industry, we're currently calling this a 'Director', as its primary purpose is to 'direct' which profiles are sent to the backend, and 'direct' the resulting MatchObjects to game servers. Further discussion / suggestions are welcome.
|
||||
- We'll be entering the design stage on longer-running MMFs before the end of the year. We'll get a proposal together and on the github repo as a request for comments, so please keep your eye out for that.
|
||||
- Match profiles providing multiple MMFs to run isn't planned anymore. Just send multiple copies of the profile with different MMFs specified via the backendapi.
|
||||
- Redis Sentinel will likely not be supported. Instead, replicated instances and HAProxy may be the HA solution of choice. There's an [outstanding issue to investigate and implement](https://github.com/GoogleCloudPlatform/open-match/issues/41) if it fills our needs, feel free to contribute!
|
||||
|
||||
## v0.1.0 (alpha)
|
||||
Initial release.
|
@ -12,17 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
FROM golang:1.14.0
|
||||
FROM golang:latest
|
||||
ENV GO111MODULE=on
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match
|
||||
|
||||
# First copy only the go.sum and go.mod then download dependencies. Docker
|
||||
# caching is [in]validated by the input files changes. So when the dependencies
|
||||
# for the project don't change, the previous image layer can be re-used. go.sum
|
||||
# is included as its hashing verifies the expected files are downloaded.
|
||||
COPY go.sum go.mod ./
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
@ -31,16 +31,15 @@ RUN sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io
|
||||
RUN export CLOUD_SDK_REPO="cloud-sdk-stretch" && \
|
||||
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
|
||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
|
||||
apt-get update -y && apt-get install google-cloud-sdk google-cloud-sdk-app-engine-go -y -qq
|
||||
apt-get update -y && apt-get install google-cloud-sdk -y -qq
|
||||
|
||||
# Install Golang
|
||||
# https://github.com/docker-library/golang/blob/master/1.14/stretch/Dockerfile
|
||||
# https://github.com/docker-library/golang/blob/fd272b2b72db82a0bd516ce3d09bba624651516c/1.12/stretch/Dockerfile
|
||||
|
||||
RUN mkdir -p /toolchain/golang
|
||||
WORKDIR /toolchain/golang
|
||||
RUN sudo rm -rf /usr/local/go/
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
RUN curl -L https://golang.org/dl/go1.14.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
|
||||
RUN curl -L https://storage.googleapis.com/golang/go1.12.1.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
@ -50,8 +49,7 @@ RUN sudo mkdir -p "$GOPATH/src" "$GOPATH/bin" \
|
||||
|
||||
# Prepare toolchain and workspace
|
||||
RUN mkdir -p /toolchain
|
||||
RUN mkdir -p /workspace
|
||||
|
||||
WORKDIR /workspace
|
||||
ENV OPEN_MATCH_CI_MODE=1
|
||||
ENV KUBECONFIG=$HOME/.kube/config
|
||||
RUN mkdir -p $HOME/.kube/
|
||||
ENV ALLOW_BUILD_WITH_SUDO=1
|
||||
|
@ -1,60 +0,0 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match
|
||||
|
||||
ARG IMAGE_TITLE
|
||||
|
||||
RUN make "build/cmd/${IMAGE_TITLE}"
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
ARG IMAGE_TITLE
|
||||
WORKDIR /app/
|
||||
|
||||
COPY --from=builder --chown=nonroot "/go/src/open-match.dev/open-match/build/cmd/${IMAGE_TITLE}/" "/app/"
|
||||
|
||||
ENTRYPOINT ["/app/run"]
|
||||
|
||||
# Docker Image Arguments
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
ARG BUILD_VERSION
|
||||
|
||||
# Standardized Docker Image Labels
|
||||
# https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
||||
LABEL \
|
||||
org.opencontainers.image.created="${BUILD_TIME}" \
|
||||
org.opencontainers.image.authors="Google LLC <open-match-discuss@googlegroups.com>" \
|
||||
org.opencontainers.image.url="https://open-match.dev/" \
|
||||
org.opencontainers.image.documentation="https://open-match.dev/site/docs/" \
|
||||
org.opencontainers.image.source="https://github.com/googleforgames/open-match" \
|
||||
org.opencontainers.image.version="${BUILD_VERSION}" \
|
||||
org.opencontainers.image.revision="1" \
|
||||
org.opencontainers.image.vendor="Google LLC" \
|
||||
org.opencontainers.image.licenses="Apache-2.0" \
|
||||
org.opencontainers.image.ref.name="" \
|
||||
org.opencontainers.image.title="${IMAGE_TITLE}" \
|
||||
org.opencontainers.image.description="Flexible, extensible, and scalable video game matchmaking." \
|
||||
org.label-schema.schema-version="1.0" \
|
||||
org.label-schema.build-date=$BUILD_DATE \
|
||||
org.label-schema.url="http://open-match.dev/" \
|
||||
org.label-schema.vcs-url="https://github.com/googleforgames/open-match" \
|
||||
org.label-schema.version=$BUILD_VERSION \
|
||||
org.label-schema.vcs-ref=$VCS_REF \
|
||||
org.label-schema.vendor="Google LLC" \
|
||||
org.label-schema.name="${IMAGE_TITLE}" \
|
||||
org.label-schema.description="Flexible, extensible, and scalable video game matchmaking." \
|
||||
org.label-schema.usage="https://open-match.dev/site/docs/"
|
115
README.md
115
README.md
@ -1,39 +1,106 @@
|
||||

|
||||

|
||||
|
||||
[](https://godoc.org/open-match.dev/open-match)
|
||||
[](https://goreportcard.com/report/open-match.dev/open-match)
|
||||
[](https://github.com/googleforgames/open-match/blob/master/LICENSE)
|
||||
[](https://github.com/googleforgames/open-match/releases)
|
||||
[](https://twitter.com/intent/follow?screen_name=Open_Match)
|
||||
[](https://godoc.org/github.com/GoogleCloudPlatform/open-match)
|
||||
[](https://goreportcard.com/report/github.com/GoogleCloudPlatform/open-match)
|
||||
[](https://github.com/GoogleCloudPlatform/open-match/blob/master/LICENSE)
|
||||
|
||||
Open Match is an open source game matchmaking framework that simplifies building
|
||||
a scalable and extensible Matchmaker. It is designed to give the game developer
|
||||
full control over how to make matches while removing the burden of dealing with
|
||||
the challenges of running a production service at scale.
|
||||
Open Match is an open source game matchmaking framework designed to allow game creators to build matchmakers of any size easily and with as much possibility for sharing and code re-use as possible. It’s designed to be flexible, extensible, and scalable.
|
||||
|
||||
Please visit [Open Match website](https://open-match.dev/site/docs/) for user
|
||||
documentation, demo instructions etc.
|
||||
Matchmaking begins when a player tells the game that they want to play. Every player has a set of attributes like skill, location, playtime, win-lose ratio, etc which may factor in how they are paired with other players. Typically, there's a trade off between the quality of the match vs the time to wait. Since Open Match is designed to scale with the player population, it should be possible to still have high quality matches while having high player count.
|
||||
|
||||
## Contributing to Open Match
|
||||
Under the covers matchmaking approaches touch on significant areas of computer science including graph theory and massively concurrent processing. Open Match is an effort to provide a foundation upon which these difficult problems can be addressed by the wider game development community. As Josh Menke — famous for working on matchmaking for many popular triple-A franchises — put it:
|
||||
|
||||
Open Match is in active development and we would love your contribution! Please
|
||||
read the [contributing guide](CONTRIBUTING.md) for guidelines on contributing to
|
||||
Open Match.
|
||||
["Matchmaking, a lot of it actually really is just really good engineering. There's a lot of really hard networking and plumbing problems that need to be solved, depending on the size of your audience."](https://youtu.be/-pglxege-gU?t=830)
|
||||
|
||||
The [Open Match Development guide](docs/development.md) has detailed instructions
|
||||
on getting the source code, making changes, testing and submitting a pull request
|
||||
to Open Match.
|
||||
This project attempts to solve the networking and plumbing problems, so game developers can focus on the logic to match players into great games.
|
||||
|
||||
## Open Match Demo
|
||||
|
||||
This section lists the steps to set up a demo for the basic functionality of Open Match. If you just want to see an E2E Open Match setup in action, please continue with this section. If you want to build Open Match from source, or modify the match functions, please follow the [Development Guide](docs/development.md).
|
||||
|
||||
### Create a Kubernetes Cluster
|
||||
|
||||
Open Match framework is a collection of servers that run within a Kubernetes cluster. Having a Kubernetes cluster is a prerequisite to deploying Open Match. If you want to deploy Open Match to an existing Kubernetes cluster, skip this step and proceed to Deploying Open Match, otherwise create a kubernetes cluster with one of the options listed below:
|
||||
|
||||
* [Set up a Google Cloud Kubernetes Cluster](docs/gcloud.md) (*this may involve extra charges unless you are on free tier*)
|
||||
* [Set up a Local Minikube cluster](https://kubernetes.io/docs/setup/minikube/)
|
||||
|
||||
### Deplying Open Match
|
||||
|
||||
Run the following steps to deploy core Open Match components and the monitoring services in the Kubernetes cluster.
|
||||
|
||||
```bash
|
||||
# Create a cluster role binding (if using gcloud)
|
||||
kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user `gcloud config get-value account`
|
||||
|
||||
# Create a cluster role binding (if using minikube)
|
||||
kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --serviceaccount=kube-system:default
|
||||
|
||||
# Create a namespace to place all the Open Match components in.
|
||||
kubectl create namespace open-match
|
||||
|
||||
# Install the core Open Match and monitoring services.
|
||||
kubectl apply -f https://github.com/GoogleCloudPlatform/open-match/releases/download/0.5.0-rc.2/install.yaml --namespace open-match
|
||||
```
|
||||
|
||||
### Deploy demo components
|
||||
|
||||
Open Match framework requires the user to author a custom match function and an evaluator that are invoked to create matches. For demo purposes, we will use an example MMF and Evaluator. The following command deploys these in the kubernetes cluster:
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/GoogleCloudPlatform/open-match/releases/download/0.5.0-rc.2/install-example.yaml --namespace open-match
|
||||
```
|
||||
|
||||
This command also deploys a component that continuously generates players with different properties and adds them to Open Match state storage. This is because a populated player pool is required to generate matches.
|
||||
|
||||
### Generate Matches!
|
||||
|
||||
In a real setup, a game backend (Director / DGS etc.) will request matches from Open Match. For demo purposes, this is simulated by a backend client that requests Open Match to continuously list matches until it runs out of players.
|
||||
|
||||
```bash
|
||||
kubectl run om-backendclient --rm --restart=Never --image-pull-policy=Always -i --tty --image=gcr.io/open-match-public-images/openmatch-backendclient:0.5.0-rc.2 --namespace=open-match
|
||||
```
|
||||
|
||||
If successful, the backend client should successfully generate matches, displaying players populated in Rosters.
|
||||
|
||||
### Cleanup
|
||||
|
||||
To delete Open Match from this cluster, simply run:
|
||||
|
||||
```bash
|
||||
kubectl delete namespace open-match
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Here are some useful links to additional documentation:
|
||||
|
||||
* [Future Roadmap](docs/roadmap.md)
|
||||
* [Open Match Concepts](docs/concepts.md)
|
||||
* [Development Guide](docs/development.md)
|
||||
* [Open Match Integrations](docs/integrations.md)
|
||||
* [References](docs/references.md)
|
||||
|
||||
For more information on the technical underpinnings of Open Match you can refer to the [docs/](docs/) directory.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read the [contributing](CONTRIBUTING.md) guide for directions on submitting Pull Requests to Open Match.
|
||||
|
||||
See the [Development guide](docs/development.md) for documentation for development and building Open Match from source.
|
||||
|
||||
Open Match is in active development - we would love your help in shaping its future!
|
||||
|
||||
## Support
|
||||
|
||||
* [Slack Channel](https://open-match.slack.com/) ([Signup](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLTM5ZWQxNjc1YWI3MzJmN2RiMWJmYWI0ZjFiNzNkZmNkMWQ3YWU5OGVkNzA5Yzc4OGVkOGU5MTc0OTA5ZTA5NDU))
|
||||
* [File an Issue](https://github.com/googleforgames/open-match/issues/new)
|
||||
* [Slack Channel](https://open-match.slack.com/) ([Signup](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU))
|
||||
* [File an Issue](https://github.com/GoogleCloudPlatform/open-match/issues/new)
|
||||
* [Mailing list](https://groups.google.com/forum/#!forum/open-match-discuss)
|
||||
* [Managed Service Survey](https://goo.gl/forms/cbrFTNCmy9rItSv72)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Participation in this project comes under the [Contributor Covenant Code of Conduct](code-of-conduct.md)
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
## Disclaimer
|
||||
This software is currently alpha, and subject to change. Although Open Match has already been used to run [production workloads within Google](https://cloud.google.com/blog/topics/inside-google-cloud/no-tricks-just-treats-globally-scaling-the-halloween-multiplayer-doodle-with-open-match-on-google-cloud), but it's still early days on the way to our final goal. There's plenty left to write and we welcome contributions. **We strongly encourage you to engage with the community through the [Slack or Mailing lists](#support) if you're considering using Open Match in production before the 1.0 release, as the documentation is likely to lag behind the latest version a bit while we focus on getting out of alpha/beta as soon as possible.**
|
||||
|
@ -1,15 +1,12 @@
|
||||
# Open Match API
|
||||
# Open Match APIs
|
||||
|
||||
Open Match API is exposed via [gRPC](https://grpc.io/) and HTTP REST with [Swagger](https://swagger.io/tools/swagger-codegen/).
|
||||
This directory contains the API specification files for Open Match. API documenation will be produced in a future version, although the protobuf files offer a concise description of the API calls available, along with arguments and return messages.
|
||||
|
||||
gRPC has first-class support for [many languages](https://grpc.io/docs/) and provides the most performance. It is a RPC protocol built on top of HTTP/2 and provides TLS for secure transport.
|
||||
* [Protobuf .proto files for all APIs](./protobuf-spec/)
|
||||
|
||||
For HTTP/HTTPS Open Match uses a gRPC proxy to serve the API. Since HTTP does not provide a structure for request/responses we use Swagger to provide a schema. You can view the Swagger docs for each service in this directory's `*.swagger.json` files. In addition each server will host it's swagger doc via `GET /swagger.json` if you want to dynamically load them at runtime.
|
||||
References:
|
||||
|
||||
Lastly, Open Match supports insecure and TLS mode for serving the API. It's strongly preferred to use TLS mode in production but insecure mode can be used for test and local development. To help with certificates management see `tools/certgen` to create self-signed certificates.
|
||||
* [gRPC](https://grpc.io/)
|
||||
* [Language Guide (proto3)](https://developers.google.com/protocol-buffers/docs/proto3)
|
||||
|
||||
# Open Match API Development Guide
|
||||
|
||||
Open Match proto comments follow the same format as [this file](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto)
|
||||
|
||||
If you plan to change the proto definitions, please update the comments and run `make api/api.md` to reflect the latest changes in open-match-docs.
|
||||
If you want to regenerate the golang gRPC modules (for local Open Match core component development, for example), the `protoc_go.sh` file in this directory may be of use to you!
|
||||
|
@ -1,171 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Backend"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
// FunctionConfig specifies a MMF address and client type for Backend to establish connections with the MMF
|
||||
message FunctionConfig {
|
||||
string host = 1;
|
||||
int32 port = 2;
|
||||
Type type = 3;
|
||||
enum Type {
|
||||
GRPC = 0;
|
||||
REST = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message FetchMatchesRequest {
|
||||
// A configuration for the MatchFunction server of this FetchMatches call.
|
||||
FunctionConfig config = 1;
|
||||
|
||||
// A MatchProfile that will be sent to the MatchFunction server of this FetchMatches call.
|
||||
MatchProfile profile = 2;
|
||||
}
|
||||
|
||||
message FetchMatchesResponse {
|
||||
// A Match generated by the user-defined MMF with the specified MatchProfiles.
|
||||
// A valid Match response will contain at least one ticket.
|
||||
Match match = 1;
|
||||
}
|
||||
|
||||
message ReleaseTicketsRequest{
|
||||
// TicketIds is a list of string representing Open Match generated Ids to be re-enabled for MMF querying
|
||||
// because they are no longer awaiting assignment from a previous match result
|
||||
repeated string ticket_ids = 1;
|
||||
}
|
||||
|
||||
message ReleaseTicketsResponse {}
|
||||
|
||||
message ReleaseAllTicketsRequest{}
|
||||
|
||||
message ReleaseAllTicketsResponse {}
|
||||
|
||||
// AssignmentGroup contains an Assignment and the Tickets to which it should be applied.
|
||||
message AssignmentGroup{
|
||||
// TicketIds is a list of strings representing Open Match generated Ids which apply to an Assignment.
|
||||
repeated string ticket_ids = 1;
|
||||
|
||||
// An Assignment specifies game connection related information to be associated with the TicketIds.
|
||||
Assignment assignment = 2;
|
||||
}
|
||||
|
||||
// AssignmentFailure contains the id of the Ticket that failed the Assignment and the failure status.
|
||||
message AssignmentFailure {
|
||||
enum Cause {
|
||||
UNKNOWN = 0;
|
||||
TICKET_NOT_FOUND = 1;
|
||||
}
|
||||
|
||||
string ticket_id = 1;
|
||||
Cause cause = 2;
|
||||
}
|
||||
|
||||
message AssignTicketsRequest {
|
||||
// Assignments is a list of assignment groups that contain assignment and the Tickets to which they should be applied.
|
||||
repeated AssignmentGroup assignments = 1;
|
||||
}
|
||||
|
||||
message AssignTicketsResponse {
|
||||
// Failures is a list of all the Tickets that failed assignment along with the cause of failure.
|
||||
repeated AssignmentFailure failures = 1;
|
||||
}
|
||||
|
||||
// The BackendService implements APIs to generate matches and handle ticket assignments.
|
||||
service BackendService {
|
||||
// FetchMatches triggers a MatchFunction with the specified MatchProfile and
|
||||
// returns a set of matches generated by the Match Making Function, and
|
||||
// accepted by the evaluator.
|
||||
// Tickets in matches returned by FetchMatches are moved from active to
|
||||
// pending, and will not be returned by query.
|
||||
rpc FetchMatches(FetchMatchesRequest) returns (stream FetchMatchesResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/matches:fetch"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// AssignTickets overwrites the Assignment field of the input TicketIds.
|
||||
rpc AssignTickets(AssignTicketsRequest) returns (AssignTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:assign"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// ReleaseTickets moves tickets from the pending state, to the active state.
|
||||
// This enables them to be returned by query, and find different matches.
|
||||
//
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc ReleaseTickets(ReleaseTicketsRequest) returns (ReleaseTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:release"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// ReleaseAllTickets moves all tickets from the pending state, to the active
|
||||
// state. This enables them to be returned by query, and find different
|
||||
// matches.
|
||||
//
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc ReleaseAllTickets(ReleaseAllTicketsRequest) returns (ReleaseAllTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:releaseall"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,566 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Backend",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/backendservice/matches:fetch": {
|
||||
"post": {
|
||||
"summary": "FetchMatches triggers a MatchFunction with the specified MatchProfile and\nreturns a set of matches generated by the Match Making Function, and\naccepted by the evaluator.\nTickets in matches returned by FetchMatches are moved from active to\npending, and will not be returned by query.",
|
||||
"operationId": "FetchMatches",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchFetchMatchesResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchFetchMatchesRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:assign": {
|
||||
"post": {
|
||||
"summary": "AssignTickets overwrites the Assignment field of the input TicketIds.",
|
||||
"operationId": "AssignTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchAssignTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchAssignTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:release": {
|
||||
"post": {
|
||||
"summary": "ReleaseTickets moves tickets from the pending state, to the active state.\nThis enables them to be returned by query, and find different matches.",
|
||||
"description": "BETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "ReleaseTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:releaseall": {
|
||||
"post": {
|
||||
"summary": "ReleaseAllTickets moves all tickets from the pending state, to the active\nstate. This enables them to be returned by query, and find different\nmatches.",
|
||||
"description": "BETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "ReleaseAllTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseAllTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseAllTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"AssignmentFailureCause": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"UNKNOWN",
|
||||
"TICKET_NOT_FOUND"
|
||||
],
|
||||
"default": "UNKNOWN"
|
||||
},
|
||||
"openmatchAssignTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchAssignmentGroup"
|
||||
},
|
||||
"description": "Assignments is a list of assignment groups that contain assignment and the Tickets to which they should be applied."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchAssignTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"failures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchAssignmentFailure"
|
||||
},
|
||||
"description": "Failures is a list of all the Tickets that failed assignment along with the cause of failure."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchAssignmentFailure": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"cause": {
|
||||
"$ref": "#/definitions/AssignmentFailureCause"
|
||||
}
|
||||
},
|
||||
"description": "AssignmentFailure contains the id of the Ticket that failed the Assignment and the failure status."
|
||||
},
|
||||
"openmatchAssignmentGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "TicketIds is a list of strings representing Open Match generated Ids which apply to an Assignment."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment specifies game connection related information to be associated with the TicketIds."
|
||||
}
|
||||
},
|
||||
"description": "AssignmentGroup contains an Assignment and the Tickets to which it should be applied."
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
},
|
||||
"openmatchFetchMatchesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/definitions/openmatchFunctionConfig",
|
||||
"description": "A configuration for the MatchFunction server of this FetchMatches call."
|
||||
},
|
||||
"profile": {
|
||||
"$ref": "#/definitions/openmatchMatchProfile",
|
||||
"description": "A MatchProfile that will be sent to the MatchFunction server of this FetchMatches call."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchFetchMatchesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Match generated by the user-defined MMF with the specified MatchProfiles.\nA valid Match response will contain at least one ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchFunctionConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/openmatchFunctionConfigType"
|
||||
}
|
||||
},
|
||||
"title": "FunctionConfig specifies a MMF address and client type for Backend to establish connections with the MMF"
|
||||
},
|
||||
"openmatchFunctionConfigType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"GRPC",
|
||||
"REST"
|
||||
],
|
||||
"default": "GRPC"
|
||||
},
|
||||
"openmatchMatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string",
|
||||
"description": "A Match ID that should be passed through the stack for tracing."
|
||||
},
|
||||
"match_profile": {
|
||||
"type": "string",
|
||||
"description": "Name of the match profile that generated this Match."
|
||||
},
|
||||
"match_function": {
|
||||
"type": "string",
|
||||
"description": "Name of the match function that generated this Match."
|
||||
},
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchMatchProfile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of this match profile."
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchPool"
|
||||
},
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "A MatchProfile is Open Match's representation of a Match specification. It is\nused to indicate the criteria for selecting players for a match. A\nMatchProfile is the input to the API to get matches and is passed to the\nMatchFunction. It contains all the information required by the MatchFunction\nto generate match proposals."
|
||||
},
|
||||
"openmatchPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
},
|
||||
"openmatchReleaseAllTicketsRequest": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchReleaseAllTicketsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchReleaseTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "TicketIds is a list of string representing Open Match generated Ids to be re-enabled for MMF querying\nbecause they are no longer awaiting assignment from a previous match result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchReleaseTicketsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchFetchMatchesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchFetchMatchesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchFetchMatchesResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Evaluator"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message EvaluateRequest {
|
||||
// A Matches proposed by the Match Function representing a candidate of the final results.
|
||||
Match match = 1;
|
||||
}
|
||||
|
||||
message EvaluateResponse {
|
||||
// A Match ID representing a shortlisted match returned by the evaluator as the final result.
|
||||
string match_id = 2;
|
||||
|
||||
// Deprecated fields
|
||||
reserved 1;
|
||||
}
|
||||
|
||||
// The Evaluator service implements APIs used to evaluate and shortlist matches proposed by MMFs.
|
||||
service Evaluator {
|
||||
// Evaluate evaluates a list of proposed matches based on quality, collision status, and etc, then shortlist the matches and returns the final results.
|
||||
rpc Evaluate(stream EvaluateRequest) returns (stream EvaluateResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/evaluator/matches:evaluate"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Evaluator",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/evaluator/matches:evaluate": {
|
||||
"post": {
|
||||
"summary": "Evaluate evaluates a list of proposed matches based on quality, collision status, and etc, then shortlist the matches and returns the final results.",
|
||||
"operationId": "Evaluate",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchEvaluateResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"description": " (streaming inputs)",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchEvaluateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Evaluator"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchEvaluateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Matches proposed by the Match Function representing a candidate of the final results."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchEvaluateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string",
|
||||
"description": "A Match ID representing a shortlisted match returned by the evaluator as the final result."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchMatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string",
|
||||
"description": "A Match ID that should be passed through the stack for tracing."
|
||||
},
|
||||
"match_profile": {
|
||||
"type": "string",
|
||||
"description": "Name of the match profile that generated this Match."
|
||||
},
|
||||
"match_function": {
|
||||
"type": "string",
|
||||
"description": "Name of the match function that generated this Match."
|
||||
},
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchEvaluateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchEvaluateResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchEvaluateResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
// A DefaultEvaluationCriteria is used for a match's evaluation_input when using
|
||||
// the default evaluator.
|
||||
message DefaultEvaluationCriteria {
|
||||
double score = 1;
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Frontend"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message CreateTicketRequest {
|
||||
// A Ticket object with SearchFields defined.
|
||||
Ticket ticket = 1;
|
||||
}
|
||||
|
||||
message DeleteTicketRequest {
|
||||
// A TicketId of a generated Ticket to be deleted.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message GetTicketRequest {
|
||||
// A TicketId of a generated Ticket.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message WatchAssignmentsRequest {
|
||||
// A TicketId of a generated Ticket to get updates on.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message WatchAssignmentsResponse {
|
||||
// An updated Assignment of the requested Ticket.
|
||||
Assignment assignment = 1;
|
||||
}
|
||||
|
||||
// The FrontendService implements APIs to manage and query status of a Tickets.
|
||||
service FrontendService {
|
||||
// CreateTicket assigns an unique TicketId to the input Ticket and record it in state storage.
|
||||
// A ticket is considered as ready for matchmaking once it is created.
|
||||
// - If a TicketId exists in a Ticket request, an auto-generated TicketId will override this field.
|
||||
// - If SearchFields exist in a Ticket, CreateTicket will also index these fields such that one can query the ticket with query.QueryTickets function.
|
||||
rpc CreateTicket(CreateTicketRequest) returns (Ticket) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/tickets"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteTicket immediately stops Open Match from using the Ticket for matchmaking and removes the Ticket from state storage.
|
||||
// The client should delete the Ticket when finished matchmaking with it.
|
||||
rpc DeleteTicket(DeleteTicketRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/frontendservice/tickets/{ticket_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// GetTicket get the Ticket associated with the specified TicketId.
|
||||
rpc GetTicket(GetTicketRequest) returns (Ticket) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/tickets/{ticket_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// WatchAssignments stream back Assignment of the specified TicketId if it is updated.
|
||||
// - If the Assignment is not updated, GetAssignment will retry using the configured backoff strategy.
|
||||
rpc WatchAssignments(WatchAssignmentsRequest)
|
||||
returns (stream WatchAssignmentsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/tickets/{ticket_id}/assignments"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,312 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Frontend",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/frontendservice/tickets": {
|
||||
"post": {
|
||||
"summary": "CreateTicket assigns an unique TicketId to the input Ticket and record it in state storage.\nA ticket is considered as ready for matchmaking once it is created.\n - If a TicketId exists in a Ticket request, an auto-generated TicketId will override this field.\n - If SearchFields exist in a Ticket, CreateTicket will also index these fields such that one can query the ticket with query.QueryTickets function.",
|
||||
"operationId": "CreateTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchCreateTicketRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/tickets/{ticket_id}": {
|
||||
"get": {
|
||||
"summary": "GetTicket get the Ticket associated with the specified TicketId.",
|
||||
"operationId": "GetTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "DeleteTicket immediately stops Open Match from using the Ticket for matchmaking and removes the Ticket from state storage.\nThe client should delete the Ticket when finished matchmaking with it.",
|
||||
"operationId": "DeleteTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket to be deleted.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/tickets/{ticket_id}/assignments": {
|
||||
"get": {
|
||||
"summary": "WatchAssignments stream back Assignment of the specified TicketId if it is updated.\n - If the Assignment is not updated, GetAssignment will retry using the configured backoff strategy.",
|
||||
"operationId": "WatchAssignments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchWatchAssignmentsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket to get updates on.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchCreateTicketRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket": {
|
||||
"$ref": "#/definitions/openmatchTicket",
|
||||
"description": "A Ticket object with SearchFields defined."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"openmatchWatchAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An updated Assignment of the requested Ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchWatchAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchWatchAssignmentsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchWatchAssignmentsResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Match Function"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message RunRequest {
|
||||
// A MatchProfile defines constraints of Tickets in a Match and shapes the Match proposed by the MatchFunction.
|
||||
MatchProfile profile = 1;
|
||||
}
|
||||
|
||||
message RunResponse {
|
||||
// A Proposal represents a Match candidate that satifies the constraints defined in the input Profile.
|
||||
// A valid Proposal response will contain at least one ticket.
|
||||
Match proposal = 1;
|
||||
}
|
||||
|
||||
// The MatchFunction service implements APIs to run user-defined matchmaking logics.
|
||||
service MatchFunction {
|
||||
// DO NOT CALL THIS FUNCTION MANUALLY. USE backend.FetchMatches INSTEAD.
|
||||
// Run pulls Tickets that satisfy Profile constraints from QueryService, runs matchmaking logics against them, then
|
||||
// constructs and streams back match candidates to the Backend service.
|
||||
rpc Run(RunRequest) returns (stream RunResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/matchfunction:run"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Match Function",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/matchfunction:run": {
|
||||
"post": {
|
||||
"summary": "DO NOT CALL THIS FUNCTION MANUALLY. USE backend.FetchMatches INSTEAD.\nRun pulls Tickets that satisfy Profile constraints from QueryService, runs matchmaking logics against them, then\nconstructs and streams back match candidates to the Backend service.",
|
||||
"operationId": "Run",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchRunResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchRunRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MatchFunction"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
},
|
||||
"openmatchMatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string",
|
||||
"description": "A Match ID that should be passed through the stack for tracing."
|
||||
},
|
||||
"match_profile": {
|
||||
"type": "string",
|
||||
"description": "Name of the match profile that generated this Match."
|
||||
},
|
||||
"match_function": {
|
||||
"type": "string",
|
||||
"description": "Name of the match function that generated this Match."
|
||||
},
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchMatchProfile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of this match profile."
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchPool"
|
||||
},
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "A MatchProfile is Open Match's representation of a Match specification. It is\nused to indicate the criteria for selecting players for a match. A\nMatchProfile is the input to the API to get matches and is passed to the\nMatchFunction. It contains all the information required by the MatchFunction\nto generate match proposals."
|
||||
},
|
||||
"openmatchPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
},
|
||||
"openmatchRunRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile": {
|
||||
"$ref": "#/definitions/openmatchMatchProfile",
|
||||
"description": "A MatchProfile defines constraints of Tickets in a Match and shapes the Match proposed by the MatchFunction."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchRunResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"proposal": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Proposal represents a Match candidate that satifies the constraints defined in the input Profile.\nA valid Proposal response will contain at least one ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchRunResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchRunResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchRunResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "google/rpc/status.proto";
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent
|
||||
// an individual 'Player', a 'Group' of players, or any other concepts unique to
|
||||
// your use case. Open Match will not interpret what the Ticket represents but
|
||||
// just treat it as a matchmaking unit with a set of SearchFields. Open Match
|
||||
// stores the Ticket in state storage and enables an Assignment to be set on the
|
||||
// Ticket.
|
||||
message Ticket {
|
||||
// Id represents an auto-generated Id issued by Open Match.
|
||||
string id = 1;
|
||||
|
||||
// An Assignment represents a game server assignment associated with a Ticket,
|
||||
// or whatever finalized matched state means for your use case.
|
||||
// Open Match does not require or inspect any fields on Assignment.
|
||||
Assignment assignment = 3;
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
SearchFields search_fields = 4;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 5;
|
||||
|
||||
// Create time is the time the Ticket was created. It is populated by Open
|
||||
// Match at the time of Ticket creation.
|
||||
google.protobuf.Timestamp create_time = 6;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2;
|
||||
}
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
message SearchFields {
|
||||
// Float arguments. Filterable on ranges.
|
||||
map<string, double> double_args = 1;
|
||||
|
||||
// String arguments. Filterable on equality.
|
||||
map<string, string> string_args = 2;
|
||||
|
||||
// Filterable on presence or absence of given value.
|
||||
repeated string tags = 3;
|
||||
}
|
||||
|
||||
// An Assignment represents a game server assignment associated with a Ticket.
|
||||
// Open Match does not require or inspect any fields on assignment.
|
||||
message Assignment {
|
||||
// Connection information for this Assignment.
|
||||
string connection = 1;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 4;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2, 3;
|
||||
}
|
||||
|
||||
// Filters numerical values to only those within a range.
|
||||
// double_arg: "foo"
|
||||
// max: 10
|
||||
// min: 5
|
||||
// matches:
|
||||
// {"foo": 5}
|
||||
// {"foo": 7.5}
|
||||
// {"foo": 10}
|
||||
// does not match:
|
||||
// {"foo": 4}
|
||||
// {"foo": 10.01}
|
||||
// {"foo": "7.5"}
|
||||
// {}
|
||||
message DoubleRangeFilter {
|
||||
// Name of the ticket's search_fields.double_args this Filter operates on.
|
||||
string double_arg = 1;
|
||||
|
||||
// Maximum value.
|
||||
double max = 2;
|
||||
|
||||
// Minimum value.
|
||||
double min = 3;
|
||||
}
|
||||
|
||||
// Filters strings exactly equaling a value.
|
||||
// string_arg: "foo"
|
||||
// value: "bar"
|
||||
// matches:
|
||||
// {"foo": "bar"}
|
||||
// does not match:
|
||||
// {"foo": "baz"}
|
||||
// {"bar": "foo"}
|
||||
// {}
|
||||
message StringEqualsFilter {
|
||||
// Name of the ticket's search_fields.string_args this Filter operates on.
|
||||
string string_arg = 1;
|
||||
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
// Filters to the tag being present on the search_fields.
|
||||
// tag: "foo"
|
||||
// matches:
|
||||
// ["foo"]
|
||||
// ["bar","foo"]
|
||||
// does not match:
|
||||
// ["bar"]
|
||||
// []
|
||||
message TagPresentFilter {
|
||||
string tag = 1;
|
||||
}
|
||||
|
||||
// Pool specfies a set of criteria that are used to select a subset of Tickets
|
||||
// that meet all the criteria.
|
||||
message Pool {
|
||||
// A developer-chosen human-readable name for this Pool.
|
||||
string name = 1;
|
||||
|
||||
// Set of Filters indicating the filtering criteria. Selected tickets must
|
||||
// match every Filter.
|
||||
repeated DoubleRangeFilter double_range_filters = 2;
|
||||
|
||||
repeated StringEqualsFilter string_equals_filters = 4;
|
||||
|
||||
repeated TagPresentFilter tag_present_filters = 5;
|
||||
|
||||
// If specified, only Tickets created before the specified time are selected.
|
||||
google.protobuf.Timestamp created_before = 6;
|
||||
|
||||
// If specified, only Tickets created after the specified time are selected.
|
||||
google.protobuf.Timestamp created_after = 7;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 3;
|
||||
}
|
||||
|
||||
// A MatchProfile is Open Match's representation of a Match specification. It is
|
||||
// used to indicate the criteria for selecting players for a match. A
|
||||
// MatchProfile is the input to the API to get matches and is passed to the
|
||||
// MatchFunction. It contains all the information required by the MatchFunction
|
||||
// to generate match proposals.
|
||||
message MatchProfile {
|
||||
// Name of this match profile.
|
||||
string name = 1;
|
||||
|
||||
// Set of pools to be queried when generating a match for this MatchProfile.
|
||||
repeated Pool pools = 3;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 5;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2, 4;
|
||||
}
|
||||
|
||||
// A Match is used to represent a completed match object. It can be generated by
|
||||
// a MatchFunction as a proposal or can be returned by OpenMatch as a result in
|
||||
// response to the FetchMatches call.
|
||||
// When a match is returned by the FetchMatches call, it should contain at least
|
||||
// one ticket to be considered as valid.
|
||||
message Match {
|
||||
// A Match ID that should be passed through the stack for tracing.
|
||||
string match_id = 1;
|
||||
|
||||
// Name of the match profile that generated this Match.
|
||||
string match_profile = 2;
|
||||
|
||||
// Name of the match function that generated this Match.
|
||||
string match_function = 3;
|
||||
|
||||
// Tickets belonging to this match.
|
||||
repeated Ticket tickets = 4;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 7;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 5, 6;
|
||||
}
|
27
api/protobuf-spec/README.md
Normal file
27
api/protobuf-spec/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
## REST compatibility
|
||||
Follow the guidelines at https://cloud.google.com/endpoints/docs/grpc/transcoding to keep the gRPC service definitions friendly to REST transcoding. An excerpt:
|
||||
|
||||
"Transcoding involves mapping HTTP/JSON requests and their parameters to gRPC methods and their parameters and return types (we'll look at exactly how you do this in the following sections). Because of this, while it's possible to map an HTTP/JSON request to any arbitrary API method, it's simplest and most intuitive to do so if the gRPC API itself is structured in a resource-oriented way, just like a traditional HTTP REST API. In other words, the API service should be designed so that it uses a small number of standard methods (corresponding to HTTP verbs like GET, PUT, and so on) that operate on the service's resources (and collections of resources, which are themselves a type of resource). These standard methods are List, Get, Create, Update, and Delete."
|
||||
|
||||
It is for these reasons we don't have gRPC calls that support bi-directional streaming in Open Match.
|
||||
|
||||
## REST API Usage
|
||||
Open Match gateway proxy transcodes any REST calls to its underlying gRPC service. Follow the [examples](https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#httprule) for further details.
|
||||
|
||||
A typical REST call to Open Match backend's `CreateAssignments` service via HTTP POST request
|
||||
```
|
||||
/v1/backend/assignments/123? \
|
||||
assignment.rosters.name=foo&assignment.rosters.players.id=1&assignment.rosters.players.id=2
|
||||
```
|
||||
is equivalent to
|
||||
|
||||
```go
|
||||
CreateAssignmentsRequest(
|
||||
Assignments(
|
||||
name: '123',
|
||||
rosters: [
|
||||
Roster(name: 'foo', [Player(id: 1), Player(id: 2)])
|
||||
]
|
||||
)
|
||||
)
|
||||
```
|
150
api/protobuf-spec/backend.proto
Normal file
150
api/protobuf-spec/backend.proto
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
import 'google/api/annotations.proto';
|
||||
|
||||
message MmfConfig {
|
||||
enum Type {
|
||||
GRPC = 0;
|
||||
REST = 1; // REST support will be added in future.
|
||||
}
|
||||
|
||||
string name = 1; // Developer-chosen, human-readable string. (Optional)
|
||||
string host = 2; // Host or DNS name for service providing this MMF. Must be resolve-able by the backend API.
|
||||
int32 port = 3; // Port number for service providing this MMF.
|
||||
Type type = 4; // Type of MMF call
|
||||
}
|
||||
|
||||
message CreateMatchRequest {
|
||||
messages.MatchObject match = 1;
|
||||
MmfConfig mmfcfg = 2;
|
||||
}
|
||||
|
||||
message CreateMatchResponse {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message ListMatchesRequest {
|
||||
messages.MatchObject match = 1;
|
||||
MmfConfig mmfcfg = 2;
|
||||
}
|
||||
|
||||
message ListMatchesResponse {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message DeleteMatchRequest {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message DeleteMatchResponse {
|
||||
}
|
||||
|
||||
message CreateAssignmentsRequest {
|
||||
messages.Assignments assignment = 1;
|
||||
}
|
||||
|
||||
message CreateAssignmentsResponse {
|
||||
}
|
||||
|
||||
message DeleteAssignmentsRequest {
|
||||
messages.Roster roster = 1;
|
||||
}
|
||||
|
||||
message DeleteAssignmentsResponse {
|
||||
}
|
||||
|
||||
service Backend {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
// INPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - [optional] roster, any fields you fill are available to your MMF.
|
||||
// - [optional] pools, any fields you fill are available to your MMF.
|
||||
// OUTPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - error. Empty if no error was encountered
|
||||
// - rosters, if you choose to fill them in your MMF. (Recommended)
|
||||
// - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)
|
||||
rpc CreateMatch(CreateMatchRequest) returns (CreateMatchResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/v1/backend/matches"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
// Continually run MMF and stream MatchObjects that fit this profile until
|
||||
// the backend client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
rpc ListMatches(ListMatchesRequest) returns (stream ListMatchesResponse) {
|
||||
option (google.api.http).get = "/v1/backend/matches/{match.id}/{match.properties}";
|
||||
}
|
||||
|
||||
// Delete a MatchObject from state storage manually. (MatchObjects in state
|
||||
// storage will also automatically expire after a while, defined in the config)
|
||||
// INPUT: MatchObject message with the 'id' field populated.
|
||||
// (All other fields are ignored.)
|
||||
rpc DeleteMatch(DeleteMatchRequest) returns (DeleteMatchResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/backend/matches"
|
||||
body: "*"
|
||||
additional_bindings {
|
||||
delete: "/v1/backend/matches/{match.id}"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Calls for communication of connection info to players.
|
||||
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.messages.Rosters to state storage. The Frontend API is
|
||||
// responsible for sending anything sent here to the game clients.
|
||||
// Sending a player to this function kicks off a process that removes
|
||||
// the player from future matchmaking functions by adding them to the
|
||||
// 'deindexed' player list and then deleting their player ID from state storage
|
||||
// indexes.
|
||||
// INPUT: Assignments message with these fields populated:
|
||||
// - assignment, anything you write to this string is sent to Frontend API
|
||||
// - rosters. You can send any number of rosters, containing any number of
|
||||
// player messages. All players from all rosters will be sent the assignment.
|
||||
// The only field in the Roster's Player messages used by CreateAssignments is
|
||||
// the id field. All other fields in the Player messages are silently ignored.
|
||||
rpc CreateAssignments(CreateAssignmentsRequest) returns (CreateAssignmentsResponse) {
|
||||
option (google.api.http)= {
|
||||
put: "/v1/backend/assignments"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
// Remove DGS connection info from state storage for players.
|
||||
// INPUT: Roster message with the 'players' field populated.
|
||||
// The only field in the Roster's Player messages used by
|
||||
// DeleteAssignments is the 'id' field. All others are silently ignored. If
|
||||
// you need to delete multiple rosters, make multiple calls.
|
||||
rpc DeleteAssignments(DeleteAssignmentsRequest) returns (DeleteAssignmentsResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/backend/assignments"
|
||||
body: "*"
|
||||
additional_bindings {
|
||||
delete: "/v1/backend/assignments"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
535
api/protobuf-spec/backend.swagger.json
Normal file
535
api/protobuf-spec/backend.swagger.json
Normal file
@ -0,0 +1,535 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "api/protobuf-spec/backend.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/backend/assignments": {
|
||||
"delete": {
|
||||
"summary": "Remove DGS connection info from state storage for players.\nINPUT: Roster message with the 'players' field populated.\n The only field in the Roster's Player messages used by\n DeleteAssignments is the 'id' field. All others are silently ignored. If\n you need to delete multiple rosters, make multiple calls.",
|
||||
"operationId": "DeleteAssignments2",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiDeleteAssignmentsResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "roster.name",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Write the connection info for the list of players in the\nAssignments.messages.Rosters to state storage. The Frontend API is\nresponsible for sending anything sent here to the game clients.\nSending a player to this function kicks off a process that removes\nthe player from future matchmaking functions by adding them to the\n'deindexed' player list and then deleting their player ID from state storage\nindexes.\nINPUT: Assignments message with these fields populated:\n - assignment, anything you write to this string is sent to Frontend API\n - rosters. You can send any number of rosters, containing any number of\n player messages. All players from all rosters will be sent the assignment.\n The only field in the Roster's Player messages used by CreateAssignments is\n the id field. All other fields in the Player messages are silently ignored.",
|
||||
"operationId": "CreateAssignments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateAssignmentsResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateAssignmentsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backend/matches": {
|
||||
"delete": {
|
||||
"summary": "Delete a MatchObject from state storage manually. (MatchObjects in state\nstorage will also automatically expire after a while, defined in the config)\nINPUT: MatchObject message with the 'id' field populated.\n(All other fields are ignored.)",
|
||||
"operationId": "DeleteMatch",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiDeleteMatchResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiDeleteMatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Run MMF once. Return a matchobject that fits this profile.\nINPUT: MatchObject message with these fields populated:\n - id\n - properties\n - [optional] roster, any fields you fill are available to your MMF.\n - [optional] pools, any fields you fill are available to your MMF.\nOUTPUT: MatchObject message with these fields populated:\n - id\n - properties\n - error. Empty if no error was encountered\n - rosters, if you choose to fill them in your MMF. (Recommended)\n - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)",
|
||||
"operationId": "CreateMatch",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateMatchResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateMatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backend/matches/{match.id}": {
|
||||
"delete": {
|
||||
"summary": "Delete a MatchObject from state storage manually. (MatchObjects in state\nstorage will also automatically expire after a while, defined in the config)\nINPUT: MatchObject message with the 'id' field populated.\n(All other fields are ignored.)",
|
||||
"operationId": "DeleteMatch2",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiDeleteMatchResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "match.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.properties",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.error",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backend/matches/{match.id}/{match.properties}": {
|
||||
"get": {
|
||||
"summary": "Continually run MMF and stream MatchObjects that fit this profile until\nthe backend client closes the connection. Same inputs/outputs as CreateMatch.",
|
||||
"operationId": "ListMatches",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/apiListMatchesResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "match.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.properties",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.error",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "mmfcfg.name",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "mmfcfg.host",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "mmfcfg.port",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "mmfcfg.type",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"GRPC",
|
||||
"REST"
|
||||
],
|
||||
"default": "GRPC"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"PlayerAttribute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreateAssignmentsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/messagesAssignments"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreateAssignmentsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiCreateMatchRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
},
|
||||
"mmfcfg": {
|
||||
"$ref": "#/definitions/apiMmfConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreateMatchResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiDeleteAssignmentsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiDeleteAssignmentsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiDeleteMatchRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiDeleteMatchResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiListMatchesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiMmfConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/apiMmfConfigType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiMmfConfigType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"GRPC",
|
||||
"REST"
|
||||
],
|
||||
"default": "GRPC"
|
||||
},
|
||||
"messagesAssignments": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
},
|
||||
"assignment": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messagesFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"minv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "A 'hard' filter to apply to the player pool."
|
||||
},
|
||||
"messagesMatchObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayerPool"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"MatchObjects\".\nIn order to request a match using the Backend API, your backend code should generate\na new MatchObject with an ID and properties filled in (for more details about valid\nvalues for these fields, see the documentation). Open Match then sends the Match\nObject through to your matchmaking function, where you add players to 'rosters' and\nstore any schemaless data you wish in the 'properties' field. The MatchObject\nis then sent, populated, out through the Backend API to your backend code.\n\nMatchObjects contain a number of fields, but many gRPC calls that take a\nMatchObject as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"pool": {
|
||||
"type": "string"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PlayerAttribute"
|
||||
}
|
||||
},
|
||||
"assignment": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"Players\".\nIn order to enter matchmaking using the Frontend API, your client code should generate\na consistent (same result for each client every time they launch) with an ID and\nproperties filled in (for more details about valid values for these fields,\nsee the documentation).\nPlayers contain a number of fields, but the gRPC calls that take a\nPlayer as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayerPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesFilter"
|
||||
}
|
||||
},
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "PlayerPools are defined by a set of 'hard' filters, and can be filled in\nwith the players that match those filters.\n\nPlayerPools contain a number of fields, but many gRPC calls that take a\nPlayerPool as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Data structure to hold a list of players in a match."
|
||||
},
|
||||
"messagesStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"elapsed": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"title": "Holds statistics"
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"apiListMatchesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/apiListMatchesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of apiListMatchesResponse"
|
||||
}
|
||||
}
|
||||
}
|
112
api/protobuf-spec/frontend.proto
Normal file
112
api/protobuf-spec/frontend.proto
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
import 'google/api/annotations.proto';
|
||||
|
||||
message CreatePlayerRequest {
|
||||
messages.Player player = 1;
|
||||
}
|
||||
|
||||
message CreatePlayerResponse {
|
||||
}
|
||||
|
||||
message DeletePlayerRequest {
|
||||
messages.Player player = 1;
|
||||
}
|
||||
|
||||
message DeletePlayerResponse {
|
||||
}
|
||||
|
||||
message GetUpdatesRequest {
|
||||
messages.Player player = 1;
|
||||
}
|
||||
|
||||
message GetUpdatesResponse {
|
||||
messages.Player player = 1;
|
||||
}
|
||||
|
||||
service Frontend {
|
||||
// Call to start matchmaking for a player
|
||||
|
||||
// CreatePlayer will put the player in state storage, and then look
|
||||
// through the 'properties' field for the attributes you have defined as
|
||||
// indices your matchmaker config. If the attributes exist and are valid
|
||||
// integers, they will be indexed.
|
||||
// INPUT: Player message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// OUTPUT: Result message denoting success or failure (and an error if
|
||||
// necessary)
|
||||
rpc CreatePlayer(CreatePlayerRequest) returns (CreatePlayerResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/v1/frontend/players"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Call to stop matchmaking for a player
|
||||
|
||||
// DeletePlayer removes the player from state storage by doing the
|
||||
// following:
|
||||
// 1) Delete player from configured indices. This effectively removes the
|
||||
// player from matchmaking when using recommended MMF patterns.
|
||||
// Everything after this is just cleanup to save stage storage space.
|
||||
// 2) 'Lazily' delete the player's state storage record. This is kicked
|
||||
// off in the background and may take some time to complete.
|
||||
// 2) 'Lazily' delete the player's metadata indicies (like, the timestamp when
|
||||
// they called CreatePlayer, and the last time the record was accessed). This
|
||||
// is also kicked off in the background and may take some time to complete.
|
||||
// INPUT: Player message with the 'id' field populated.
|
||||
// OUTPUT: Result message denoting success or failure (and an error if
|
||||
// necessary)
|
||||
rpc DeletePlayer(DeletePlayerRequest) returns (DeletePlayerResponse) {
|
||||
option (google.api.http).delete = "/v1/frontend/players/{player.id}";
|
||||
}
|
||||
|
||||
// Calls to access matchmaking results for a player
|
||||
|
||||
// GetUpdates streams matchmaking results from Open Match for the
|
||||
// provided player ID.
|
||||
// INPUT: Player message with the 'id' field populated.
|
||||
// OUTPUT: a stream of player objects with one or more of the following
|
||||
// fields populated, if an update to that field is seen in state storage:
|
||||
// - 'assignment': string that usually contains game server connection information.
|
||||
// - 'status': string to communicate current matchmaking status to the client.
|
||||
// - 'error': string to pass along error information to the client.
|
||||
//
|
||||
// During normal operation, the expectation is that the 'assignment' field
|
||||
// will be updated by a Backend process calling the 'CreateAssignments' Backend API
|
||||
// endpoint. 'Status' and 'Error' are free for developers to use as they see fit.
|
||||
// Even if you had multiple players enter a matchmaking request as a group, the
|
||||
// Backend API 'CreateAssignments' call will write the results to state
|
||||
// storage separately under each player's ID. OM expects you to make all game
|
||||
// clients 'GetUpdates' with their own ID from the Frontend API to get
|
||||
// their results.
|
||||
//
|
||||
// NOTE: This call generates a small amount of load on the Frontend API and state
|
||||
// storage while watching the player record for updates. You are expected
|
||||
// to close the stream from your client after receiving your matchmaking
|
||||
// results (or a reasonable timeout), or you will continue to
|
||||
// generate load on OM until you do!
|
||||
// NOTE: Just bear in mind that every update will send egress traffic from
|
||||
// Open Match to game clients! Frugality is recommended.
|
||||
rpc GetUpdates(GetUpdatesRequest) returns (stream GetUpdatesResponse) {
|
||||
option (google.api.http).get = "/v1/frontend/players/{player.id}";
|
||||
}
|
||||
}
|
272
api/protobuf-spec/frontend.swagger.json
Normal file
272
api/protobuf-spec/frontend.swagger.json
Normal file
@ -0,0 +1,272 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "api/protobuf-spec/frontend.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/frontend/players": {
|
||||
"put": {
|
||||
"summary": "CreatePlayer will put the player in state storage, and then look\nthrough the 'properties' field for the attributes you have defined as\nindices your matchmaker config. If the attributes exist and are valid\nintegers, they will be indexed.\nINPUT: Player message with these fields populated:\n - id\n - properties\nOUTPUT: Result message denoting success or failure (and an error if\nnecessary)",
|
||||
"operationId": "CreatePlayer",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreatePlayerResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreatePlayerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Frontend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontend/players/{player.id}": {
|
||||
"get": {
|
||||
"summary": "GetUpdates streams matchmaking results from Open Match for the\nprovided player ID.\nINPUT: Player message with the 'id' field populated.\nOUTPUT: a stream of player objects with one or more of the following\nfields populated, if an update to that field is seen in state storage:\n - 'assignment': string that usually contains game server connection information.\n - 'status': string to communicate current matchmaking status to the client.\n - 'error': string to pass along error information to the client.",
|
||||
"description": "During normal operation, the expectation is that the 'assignment' field\nwill be updated by a Backend process calling the 'CreateAssignments' Backend API\nendpoint. 'Status' and 'Error' are free for developers to use as they see fit. \nEven if you had multiple players enter a matchmaking request as a group, the\nBackend API 'CreateAssignments' call will write the results to state\nstorage separately under each player's ID. OM expects you to make all game\nclients 'GetUpdates' with their own ID from the Frontend API to get\ntheir results.\n\nNOTE: This call generates a small amount of load on the Frontend API and state\n storage while watching the player record for updates. You are expected\n to close the stream from your client after receiving your matchmaking\n results (or a reasonable timeout), or you will continue to\n generate load on OM until you do!\nNOTE: Just bear in mind that every update will send egress traffic from\n Open Match to game clients! Frugality is recommended.",
|
||||
"operationId": "GetUpdates",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/apiGetUpdatesResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "player.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.properties",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.pool",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.assignment",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.error",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Frontend"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "DeletePlayer removes the player from state storage by doing the\nfollowing:\n 1) Delete player from configured indices. This effectively removes the\n player from matchmaking when using recommended MMF patterns.\n Everything after this is just cleanup to save stage storage space.\n 2) 'Lazily' delete the player's state storage record. This is kicked\n off in the background and may take some time to complete.\n 2) 'Lazily' delete the player's metadata indicies (like, the timestamp when \n they called CreatePlayer, and the last time the record was accessed). This \n is also kicked off in the background and may take some time to complete.\nINPUT: Player message with the 'id' field populated.\nOUTPUT: Result message denoting success or failure (and an error if\nnecessary)",
|
||||
"operationId": "DeletePlayer",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiDeletePlayerResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "player.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.properties",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.pool",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.assignment",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player.error",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Frontend"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"PlayerAttribute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreatePlayerRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"player": {
|
||||
"$ref": "#/definitions/messagesPlayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreatePlayerResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiDeletePlayerResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiGetUpdatesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"player": {
|
||||
"$ref": "#/definitions/messagesPlayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messagesPlayer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"pool": {
|
||||
"type": "string"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PlayerAttribute"
|
||||
}
|
||||
},
|
||||
"assignment": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"Players\".\nIn order to enter matchmaking using the Frontend API, your client code should generate\na consistent (same result for each client every time they launch) with an ID and\nproperties filled in (for more details about valid values for these fields,\nsee the documentation).\nPlayers contain a number of fields, but the gRPC calls that take a\nPlayer as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"apiGetUpdatesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/apiGetUpdatesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of apiGetUpdatesResponse"
|
||||
}
|
||||
}
|
||||
}
|
48
api/protobuf-spec/matchfunction.proto
Normal file
48
api/protobuf-spec/matchfunction.proto
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
import 'google/api/annotations.proto';
|
||||
|
||||
// Request message sent to the MMF.
|
||||
message RunRequest {
|
||||
string profile_id = 1; // Developer-chosen profile name, state storage key for the match object.
|
||||
string proposal_id = 2; // The ID against which, the generated proposal should be stored.
|
||||
string result_id = 3; // Final result ID. MMF needs to know this in case of errors where proposal generation can be shortcircuited.
|
||||
messages.MatchObject match_object = 4; // The match object containing the details of the match to be generated.
|
||||
string timestamp = 5;
|
||||
}
|
||||
|
||||
message RunResponse {
|
||||
}
|
||||
|
||||
// The MMF proto defines the API for running MMFs as long-lived, 'serving'
|
||||
// functions inside of the kubernetes cluster.
|
||||
service MatchFunction {
|
||||
// The assumption is that there will be one service for each MMF that is
|
||||
// being served. Build your MMF in the appropriate serving harness, deploy it
|
||||
// to the K8s cluster with a unique service name, then connect to that service
|
||||
// and call 'Run()' to execute the fuction.
|
||||
rpc Run(RunRequest) returns (RunResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/v1/function"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
217
api/protobuf-spec/matchfunction.swagger.json
Normal file
217
api/protobuf-spec/matchfunction.swagger.json
Normal file
@ -0,0 +1,217 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "api/protobuf-spec/matchfunction.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/function": {
|
||||
"put": {
|
||||
"summary": "The assumption is that there will be one service for each MMF that is\nbeing served. Build your MMF in the appropriate serving harness, deploy it\nto the K8s cluster with a unique service name, then connect to that service\nand call 'Run()' to execute the fuction.",
|
||||
"operationId": "Run",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiRunResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiRunRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MatchFunction"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"PlayerAttribute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiRunRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"proposal_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"match_object": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Request message sent to the MMF."
|
||||
},
|
||||
"apiRunResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"messagesFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"minv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "A 'hard' filter to apply to the player pool."
|
||||
},
|
||||
"messagesMatchObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayerPool"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"MatchObjects\".\nIn order to request a match using the Backend API, your backend code should generate\na new MatchObject with an ID and properties filled in (for more details about valid\nvalues for these fields, see the documentation). Open Match then sends the Match\nObject through to your matchmaking function, where you add players to 'rosters' and\nstore any schemaless data you wish in the 'properties' field. The MatchObject\nis then sent, populated, out through the Backend API to your backend code.\n\nMatchObjects contain a number of fields, but many gRPC calls that take a\nMatchObject as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"pool": {
|
||||
"type": "string"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PlayerAttribute"
|
||||
}
|
||||
},
|
||||
"assignment": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"Players\".\nIn order to enter matchmaking using the Frontend API, your client code should generate\na consistent (same result for each client every time they launch) with an ID and\nproperties filled in (for more details about valid values for these fields,\nsee the documentation).\nPlayers contain a number of fields, but the gRPC calls that take a\nPlayer as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayerPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesFilter"
|
||||
}
|
||||
},
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "PlayerPools are defined by a set of 'hard' filters, and can be filled in\nwith the players that match those filters.\n\nPlayerPools contain a number of fields, but many gRPC calls that take a\nPlayerPool as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Data structure to hold a list of players in a match."
|
||||
},
|
||||
"messagesStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"elapsed": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"title": "Holds statistics"
|
||||
}
|
||||
}
|
||||
}
|
102
api/protobuf-spec/messages.proto
Normal file
102
api/protobuf-spec/messages.proto
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = 'proto3';
|
||||
package messages;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
// Open Match's internal representation and wire protocol format for "MatchObjects".
|
||||
// In order to request a match using the Backend API, your backend code should generate
|
||||
// a new MatchObject with an ID and properties filled in (for more details about valid
|
||||
// values for these fields, see the documentation). Open Match then sends the Match
|
||||
// Object through to your matchmaking function, where you add players to 'rosters' and
|
||||
// store any schemaless data you wish in the 'properties' field. The MatchObject
|
||||
// is then sent, populated, out through the Backend API to your backend code.
|
||||
//
|
||||
// MatchObjects contain a number of fields, but many gRPC calls that take a
|
||||
// MatchObject as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
message MatchObject {
|
||||
string id = 1; // By convention, an Xid
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
string error = 3; // Last error encountered.
|
||||
repeated Roster rosters = 4; // Rosters of players.
|
||||
repeated PlayerPool pools = 5; // 'Hard' filters, and the players who match them.
|
||||
string status = 6; // Resulting status of the match function
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
message Roster {
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
repeated Player players = 2; // Player profiles on this roster.
|
||||
}
|
||||
|
||||
// A 'hard' filter to apply to the player pool.
|
||||
message Filter {
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
string attribute = 2; // Name of the player attribute this filter operates on.
|
||||
int64 maxv = 3; // Maximum value. Defaults to positive infinity (any value above minv).
|
||||
int64 minv = 4; // Minimum value. Defaults to 0.
|
||||
Stats stats = 5; // Statistics for the last time the filter was applied.
|
||||
}
|
||||
|
||||
// Holds statistics
|
||||
message Stats {
|
||||
int64 count = 1; // Number of results.
|
||||
double elapsed = 2; // How long it took to get the results.
|
||||
}
|
||||
|
||||
// PlayerPools are defined by a set of 'hard' filters, and can be filled in
|
||||
// with the players that match those filters.
|
||||
//
|
||||
// PlayerPools contain a number of fields, but many gRPC calls that take a
|
||||
// PlayerPool as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
message PlayerPool {
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable string.
|
||||
repeated Filter filters = 2; // Filters are logical AND-ed (a player must match every filter).
|
||||
Roster roster = 3; // Roster of players that match all filters.
|
||||
Stats stats = 4; // Statisticss for the last time this Pool was retrieved from state storage.
|
||||
}
|
||||
|
||||
// Open Match's internal representation and wire protocol format for "Players".
|
||||
// In order to enter matchmaking using the Frontend API, your client code should generate
|
||||
// a consistent (same result for each client every time they launch) with an ID and
|
||||
// properties filled in (for more details about valid values for these fields,
|
||||
// see the documentation).
|
||||
// Players contain a number of fields, but the gRPC calls that take a
|
||||
// Player as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
message Player {
|
||||
message Attribute {
|
||||
string name = 1; // Name should match a Filter.attribute field.
|
||||
int64 value = 2;
|
||||
}
|
||||
string id = 1; // By convention, an Xid
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
string pool = 3; // Optionally used to specify the PlayerPool in which to find a player.
|
||||
repeated Attribute attributes = 4; // Attributes of this player.
|
||||
string assignment = 5; // By convention, ip:port of a DGS to connect to
|
||||
string status = 6; // Arbitrary developer-chosen string.
|
||||
string error = 7; // Arbitrary developer-chosen string.
|
||||
}
|
||||
|
||||
// IlInput is an empty message reserved for future use.
|
||||
message IlInput {
|
||||
}
|
||||
|
||||
message Assignments {
|
||||
repeated Roster rosters = 1;
|
||||
string assignment = 10;
|
||||
}
|
137
api/protobuf-spec/mmlogic.proto
Normal file
137
api/protobuf-spec/mmlogic.proto
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
import 'google/api/annotations.proto';
|
||||
|
||||
message GetProfileRequest {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message GetProfileResponse {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message CreateProposalRequest {
|
||||
messages.MatchObject match = 1;
|
||||
}
|
||||
|
||||
message CreateProposalResponse {
|
||||
}
|
||||
|
||||
message GetPlayerPoolRequest {
|
||||
messages.PlayerPool player_pool = 1;
|
||||
}
|
||||
|
||||
message GetPlayerPoolResponse {
|
||||
messages.PlayerPool player_pool = 1;
|
||||
}
|
||||
|
||||
message GetAllIgnoredPlayersRequest {
|
||||
messages.IlInput ignore_player = 1;
|
||||
}
|
||||
|
||||
message GetAllIgnoredPlayersResponse {
|
||||
messages.Roster roster = 1;
|
||||
}
|
||||
|
||||
message ListIgnoredPlayersRequest {
|
||||
messages.IlInput ignore_player = 1;
|
||||
}
|
||||
|
||||
message ListIgnoredPlayersResponse {
|
||||
messages.Roster roster = 1;
|
||||
}
|
||||
|
||||
// The MMLogic API provides utility functions for common MMF functionality, such
|
||||
// as retreiving profiles and players from state storage, writing results to state storage,
|
||||
// and exposing metrics and statistics.
|
||||
service MmLogic {
|
||||
// Profile and match object functions
|
||||
|
||||
// Send GetProfile a match object with the ID field populated, it will return a
|
||||
// 'filled' one.
|
||||
// Note: filters are assumed to have been checked for validity by the
|
||||
// backendapi when accepting a profile
|
||||
rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) {
|
||||
option (google.api.http).get = "/v1/logic/match-profiles/{match.id}";
|
||||
}
|
||||
|
||||
// CreateProposal is called by MMFs that wish to write their results to
|
||||
// a proposed MatchObject, that can be sent out the Backend API once it has
|
||||
// been approved (by default, by the evaluator process).
|
||||
// - adds all players in all Rosters to the proposed player ignore list
|
||||
// - writes the proposed match to the provided key
|
||||
// - adds that key to the list of proposals to be considered
|
||||
// INPUT:
|
||||
// * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN
|
||||
// To create a match MatchObject message with these fields populated:
|
||||
// - id, set to the value of the MMF_PROPOSAL_ID env var
|
||||
// - properties
|
||||
// - error. You must explicitly set this to an empty string if your MMF
|
||||
// - roster, with the playerIDs filled in the 'players' repeated field.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// was successful.
|
||||
// * TO RETURN AN ERROR
|
||||
// To report a failure or error, send a MatchObject message with these
|
||||
// these fields populated:
|
||||
// - id, set to the value of the MMF_ERROR_ID env var.
|
||||
// - error, set to a string value describing the error your MMF encountered.
|
||||
// - [optional] properties, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] rosters, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// OUTPUT: a Result message with a boolean success value and an error string
|
||||
// if an error was encountered
|
||||
rpc CreateProposal(CreateProposalRequest) returns (CreateProposalResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/v1/logic/match-proposals"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Player listing and filtering functions
|
||||
//
|
||||
// RetrievePlayerPool gets the list of players that match every Filter in the
|
||||
// PlayerPool, .excluding players in any configured ignore lists. It
|
||||
// combines the results, and returns the resulting player pool.
|
||||
rpc GetPlayerPool(GetPlayerPoolRequest) returns (stream GetPlayerPoolResponse) {
|
||||
option (google.api.http).get = "/v1/logic/player-pools/{player_pool.name}";
|
||||
}
|
||||
|
||||
// Ignore List functions
|
||||
//
|
||||
// IlInput is an empty message reserved for future use.
|
||||
rpc GetAllIgnoredPlayers(GetAllIgnoredPlayersRequest) returns (GetAllIgnoredPlayersResponse) {}
|
||||
// ListIgnoredPlayers retrieves players from the ignore list specified in the
|
||||
// config file under 'ignoreLists.proposed.name'.
|
||||
rpc ListIgnoredPlayers(ListIgnoredPlayersRequest) returns (ListIgnoredPlayersResponse) {}
|
||||
|
||||
// NYI
|
||||
// UpdateMetrics sends stats about the MMF run to export to a metrics aggregation tool
|
||||
// like Prometheus or StackDriver.
|
||||
// rpc UpdateMetrics(messages.NYI) returns (messages.Results) {}
|
||||
}
|
380
api/protobuf-spec/mmlogic.swagger.json
Normal file
380
api/protobuf-spec/mmlogic.swagger.json
Normal file
@ -0,0 +1,380 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "api/protobuf-spec/mmlogic.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/logic/match-profiles/{match.id}": {
|
||||
"get": {
|
||||
"summary": "Send GetProfile a match object with the ID field populated, it will return a\n 'filled' one.\n Note: filters are assumed to have been checked for validity by the\n backendapi when accepting a profile",
|
||||
"operationId": "GetProfile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiGetProfileResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "match.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.properties",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.error",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "match.status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MmLogic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/logic/match-proposals": {
|
||||
"put": {
|
||||
"summary": "CreateProposal is called by MMFs that wish to write their results to\na proposed MatchObject, that can be sent out the Backend API once it has\nbeen approved (by default, by the evaluator process).\n - adds all players in all Rosters to the proposed player ignore list\n - writes the proposed match to the provided key\n - adds that key to the list of proposals to be considered\nINPUT: \n * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN\n To create a match MatchObject message with these fields populated:\n - id, set to the value of the MMF_PROPOSAL_ID env var\n - properties\n - error. You must explicitly set this to an empty string if your MMF\n - roster, with the playerIDs filled in the 'players' repeated field. \n - [optional] pools, set to the output from the 'GetPlayerPools' call,\n will populate the pools with stats about how many players the filters\n matched and how long the filters took to run, which will be sent out\n the backend api along with your match results.\n was successful.\n * TO RETURN AN ERROR \n To report a failure or error, send a MatchObject message with these\n these fields populated:\n - id, set to the value of the MMF_ERROR_ID env var. \n - error, set to a string value describing the error your MMF encountered.\n - [optional] properties, anything you put here is returned to the\n backend along with your error.\n - [optional] rosters, anything you put here is returned to the\n backend along with your error.\n - [optional] pools, set to the output from the 'GetPlayerPools' call,\n will populate the pools with stats about how many players the filters\n matched and how long the filters took to run, which will be sent out\n the backend api along with your match results.\nOUTPUT: a Result message with a boolean success value and an error string\nif an error was encountered",
|
||||
"operationId": "CreateProposal",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateProposalResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiCreateProposalRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MmLogic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/logic/player-pools/{player_pool.name}": {
|
||||
"get": {
|
||||
"summary": "Player listing and filtering functions",
|
||||
"description": "RetrievePlayerPool gets the list of players that match every Filter in the\nPlayerPool, .excluding players in any configured ignore lists. It\ncombines the results, and returns the resulting player pool.",
|
||||
"operationId": "GetPlayerPool",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/apiGetPlayerPoolResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "player_pool.name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player_pool.roster.name",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "player_pool.stats.count",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "player_pool.stats.elapsed",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MmLogic"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"PlayerAttribute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreateProposalRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiCreateProposalResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"apiGetAllIgnoredPlayersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiGetPlayerPoolResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"player_pool": {
|
||||
"$ref": "#/definitions/messagesPlayerPool"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiGetProfileResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/messagesMatchObject"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apiListIgnoredPlayersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messagesFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"minv": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "A 'hard' filter to apply to the player pool."
|
||||
},
|
||||
"messagesIlInput": {
|
||||
"type": "object",
|
||||
"description": "IlInput is an empty message reserved for future use."
|
||||
},
|
||||
"messagesMatchObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
}
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayerPool"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"MatchObjects\".\nIn order to request a match using the Backend API, your backend code should generate\na new MatchObject with an ID and properties filled in (for more details about valid\nvalues for these fields, see the documentation). Open Match then sends the Match\nObject through to your matchmaking function, where you add players to 'rosters' and\nstore any schemaless data you wish in the 'properties' field. The MatchObject\nis then sent, populated, out through the Backend API to your backend code.\n\nMatchObjects contain a number of fields, but many gRPC calls that take a\nMatchObject as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
},
|
||||
"pool": {
|
||||
"type": "string"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PlayerAttribute"
|
||||
}
|
||||
},
|
||||
"assignment": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Open Match's internal representation and wire protocol format for \"Players\".\nIn order to enter matchmaking using the Frontend API, your client code should generate\na consistent (same result for each client every time they launch) with an ID and\nproperties filled in (for more details about valid values for these fields,\nsee the documentation).\nPlayers contain a number of fields, but the gRPC calls that take a\nPlayer as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesPlayerPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesFilter"
|
||||
}
|
||||
},
|
||||
"roster": {
|
||||
"$ref": "#/definitions/messagesRoster"
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/definitions/messagesStats"
|
||||
}
|
||||
},
|
||||
"description": "PlayerPools are defined by a set of 'hard' filters, and can be filled in\nwith the players that match those filters.\n\nPlayerPools contain a number of fields, but many gRPC calls that take a\nPlayerPool as input only require a few of them to be filled in. Check the\ngRPC function in question for more details."
|
||||
},
|
||||
"messagesRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/messagesPlayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Data structure to hold a list of players in a match."
|
||||
},
|
||||
"messagesStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"elapsed": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"title": "Holds statistics"
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"apiGetPlayerPoolResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/apiGetPlayerPoolResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of apiGetPlayerPoolResponse"
|
||||
}
|
||||
}
|
||||
}
|
101
api/query.proto
101
api/query.proto
@ -1,101 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "MM Logic (Data Layer)"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message QueryTicketsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
message QueryTicketsResponse {
|
||||
// Tickets that meet all the filtering criteria requested by the pool.
|
||||
repeated Ticket tickets = 1;
|
||||
}
|
||||
|
||||
message QueryTicketIdsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
message QueryTicketIdsResponse {
|
||||
// TicketIDs that meet all the filtering criteria requested by the pool.
|
||||
repeated string ids = 1;
|
||||
}
|
||||
|
||||
// The QueryService service implements helper APIs for Match Function to query Tickets from state storage.
|
||||
service QueryService {
|
||||
// QueryTickets gets a list of Tickets that match all Filters of the input Pool.
|
||||
// - If the Pool contains no Filters, QueryTickets will return all Tickets in the state storage.
|
||||
// QueryTickets pages the Tickets by `queryPageSize` and stream back responses.
|
||||
// - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.
|
||||
rpc QueryTickets(QueryTicketsRequest) returns (stream QueryTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/tickets:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// QueryTicketIds gets the list of TicketIDs that meet all the filtering criteria requested by the pool.
|
||||
// - If the Pool contains no Filters, QueryTicketIds will return all TicketIDs in the state storage.
|
||||
// QueryTicketIds pages the TicketIDs by `queryPageSize` and stream back responses.
|
||||
// - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.
|
||||
rpc QueryTicketIds(QueryTicketIdsRequest) returns (stream QueryTicketIdsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/ticketids:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,366 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "MM Logic (Data Layer)",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/queryservice/ticketids:query": {
|
||||
"post": {
|
||||
"summary": "QueryTicketIds gets the list of TicketIDs that meet all the filtering criteria requested by the pool.\n - If the Pool contains no Filters, QueryTicketIds will return all TicketIDs in the state storage.\nQueryTicketIds pages the TicketIDs by `queryPageSize` and stream back responses.\n - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.",
|
||||
"operationId": "QueryTicketIds",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchQueryTicketIdsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketIdsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/queryservice/tickets:query": {
|
||||
"post": {
|
||||
"summary": "QueryTickets gets a list of Tickets that match all Filters of the input Pool.\n - If the Pool contains no Filters, QueryTickets will return all Tickets in the state storage.\nQueryTickets pages the Tickets by `queryPageSize` and stream back responses.\n - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.",
|
||||
"operationId": "QueryTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
},
|
||||
"openmatchPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
},
|
||||
"openmatchQueryTicketIdsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketIdsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "TicketIDs that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "Tickets that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchQueryTicketIdsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketIdsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketIdsResponse"
|
||||
},
|
||||
"openmatchQueryTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
155
cloudbuild.yaml
155
cloudbuild.yaml
@ -1,4 +1,4 @@
|
||||
# Copyright 2019 Google LLC
|
||||
# Copyright 2019 Google Inc. 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.
|
||||
@ -47,21 +47,17 @@
|
||||
# https://github.com/GoogleCloudPlatform/cloud-builders/tree/master/go
|
||||
|
||||
steps:
|
||||
# Blocked by https://github.com/GoogleContainerTools/kaniko/issues/477
|
||||
- id: 'Docker Image: open-match-build'
|
||||
name: gcr.io/cloud-builders/docker
|
||||
args: ['build', '-t', 'gcr.io/$PROJECT_ID/open-match-build', '-f', 'Dockerfile.ci', '.']
|
||||
name: gcr.io/kaniko-project/executor
|
||||
args: ['--destination=gcr.io/$PROJECT_ID/open-match-build', '--cache=true', '--cache-ttl=6h', '--dockerfile=Dockerfile.ci', '.']
|
||||
waitFor: ['-']
|
||||
|
||||
- id: 'Build: Clean'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'clean-third-party', 'clean-protos', 'clean-swagger-docs']
|
||||
args: ['make', 'clean']
|
||||
waitFor: ['Docker Image: open-match-build']
|
||||
|
||||
# - id: 'Test: Markdown'
|
||||
# name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
# args: ['make', 'md-test']
|
||||
# waitFor: ['Build: Clean']
|
||||
|
||||
- id: 'Setup: Download Dependencies'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'sync-deps']
|
||||
@ -70,7 +66,7 @@ steps:
|
||||
path: '/go'
|
||||
waitFor: ['Build: Clean']
|
||||
|
||||
- id: 'Build: Initialize Toolchain'
|
||||
- id: 'Build: Install Toolchain'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'install-toolchain']
|
||||
volumes:
|
||||
@ -78,44 +74,41 @@ steps:
|
||||
path: '/go'
|
||||
waitFor: ['Setup: Download Dependencies']
|
||||
|
||||
- id: 'Test: Terraform Configuration'
|
||||
- id: 'Build: Protocol Buffers'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'terraform-test']
|
||||
waitFor: ['Build: Initialize Toolchain']
|
||||
|
||||
- id: 'Build: Deployment Configs'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'SHORT_SHA=${SHORT_SHA}', 'update-chart-deps', 'install/yaml/']
|
||||
waitFor: ['Build: Initialize Toolchain']
|
||||
|
||||
- id: 'Build: Assets'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'assets', '-j12']
|
||||
args: ['make', 'all-protos']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Deployment Configs']
|
||||
waitFor: ['Build: Install Toolchain']
|
||||
|
||||
- id: 'Build: Binaries'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GOPROXY=off', 'build', 'all', '-j12']
|
||||
args: ['make', 'GOPROXY=off', 'all', '-j8']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
|
||||
- id: 'Test: Services'
|
||||
waitFor: ['Build: Protocol Buffers']
|
||||
- id: 'Test: Core'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GOLANG_TEST_COUNT=10', 'test']
|
||||
args: ['make', 'GOPROXY=off', 'test-in-ci']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
|
||||
waitFor: ['Build: Protocol Buffers']
|
||||
- id: 'Build: Docker Images'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', '_GCB_LATEST_VERSION=${_GCB_LATEST_VERSION}', 'SHORT_SHA=${SHORT_SHA}', 'BRANCH_NAME=${BRANCH_NAME}', 'push-images', '-j8']
|
||||
waitFor: ['Build: Assets']
|
||||
args: ['make', 'VERSION_SUFFIX=$SHORT_SHA', 'build-images', '-j8']
|
||||
waitFor: ['Build: Protocol Buffers']
|
||||
- id: 'Build: Push Images'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'VERSION_SUFFIX=$SHORT_SHA', 'push-images', '-j8']
|
||||
waitFor: ['Build: Docker Images']
|
||||
|
||||
- id: 'Build: Deployment Configs'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'VERSION_SUFFIX=$SHORT_SHA', 'clean-install-yaml', 'install/yaml/']
|
||||
waitFor: ['Build: Install Toolchain']
|
||||
|
||||
- id: 'Lint: Format, Vet, Charts'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
@ -123,54 +116,92 @@ steps:
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
waitFor: ['Build: Protocol Buffers', 'Build: Deployment Configs']
|
||||
|
||||
- id: 'Test: Deploy Open Match'
|
||||
- id: 'Build: Website'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'OPEN_MATCH_RELEASE_NAME=open-match-${BUILD_ID}', 'auth-gke-cluster', 'delete-chart', 'ci-reap-namespaces', 'install-ci-chart']
|
||||
waitFor: ['Build: Docker Images']
|
||||
args: ['make', 'build/site/']
|
||||
waitFor: ['Build: Install Toolchain']
|
||||
|
||||
- id: 'Test: Website'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'site-test']
|
||||
waitFor: ['Build: Website']
|
||||
|
||||
- id: 'Deploy: Website'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', VERSION_SUFFIX=$SHORT_SHA', 'BRANCH_NAME=$BRANCH_NAME', 'ci-deploy-dev-site']
|
||||
waitFor: ['Test: Website', 'Build: Binaries']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
|
||||
- id: 'Deploy: Deployment Configs'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', '_GCB_LATEST_VERSION=${_GCB_LATEST_VERSION}', 'SHORT_SHA=${SHORT_SHA}', 'BRANCH_NAME=${BRANCH_NAME}', 'ci-deploy-artifacts']
|
||||
waitFor: ['Lint: Format, Vet, Charts', 'Test: Deploy Open Match']
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', VERSION_SUFFIX=$SHORT_SHA', 'BRANCH_NAME=$BRANCH_NAME', 'ci-deploy-artifacts']
|
||||
waitFor: ['Lint: Format, Vet, Charts', 'Build: Binaries']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
|
||||
- id: 'Test: End-to-End Cluster'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GOPROXY=off', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'test-e2e-cluster']
|
||||
waitFor: ['Test: Deploy Open Match', 'Build: Assets']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
#- id: 'Deploy: Create Cluster'
|
||||
# name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
# args: ['make', 'create-gke-cluster', 'push-helm']
|
||||
# waitFor: ['Build: Docker Images']
|
||||
|
||||
- id: 'Test: Delete Open Match'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GCLOUD_EXTRA_FLAGS=--async', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'OPEN_MATCH_RELEASE_NAME=open-match-${BUILD_ID}', 'GCP_PROJECT_ID=${PROJECT_ID}', 'delete-chart']
|
||||
waitFor: ['Test: End-to-End Cluster']
|
||||
#- id: 'Deploy: Install Charts'
|
||||
# name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
# args: ['make', 'sleep-10', 'install-chart', 'install-example-chart']
|
||||
# waitFor: ['Deploy: Create Cluster']
|
||||
|
||||
#- id: 'Deploy: Teardown Cluster'
|
||||
# name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
# args: ['make', 'sleep-10', 'delete-gke-cluster']
|
||||
# waitFor: ['Deploy: Install Charts']
|
||||
|
||||
artifacts:
|
||||
objects:
|
||||
location: '${_ARTIFACTS_BUCKET}'
|
||||
location: gs://open-match-build-artifacts/output/
|
||||
paths:
|
||||
- cmd/minimatch/minimatch
|
||||
- cmd/backendapi/backendapi
|
||||
- cmd/frontendapi/frontendapi
|
||||
- cmd/mmlogicapi/mmlogicapi
|
||||
- examples/functions/golang/grpc-serving/grpc-serving
|
||||
- examples/evaluators/golang/serving/serving
|
||||
- examples/backendclient/backendclient
|
||||
- test/cmd/clientloadgen/clientloadgen
|
||||
- test/cmd/frontendclient/frontendclient
|
||||
- install/yaml/install.yaml
|
||||
- install/yaml/01-open-match-core.yaml
|
||||
- install/yaml/02-open-match-demo.yaml
|
||||
- install/yaml/install-example.yaml
|
||||
- install/yaml/01-redis-chart.yaml
|
||||
- install/yaml/02-open-match.yaml
|
||||
- install/yaml/03-prometheus-chart.yaml
|
||||
- install/yaml/04-grafana-chart.yaml
|
||||
- install/yaml/05-jaeger-chart.yaml
|
||||
- install/yaml/06-open-match-override-configmap.yaml
|
||||
|
||||
images:
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-minimatch:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-backendapi:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-frontendapi:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-mmlogicapi:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-evaluator-serving:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-mmf-go-grpc-serving-simple:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-backendclient:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-clientloadgen:${_OM_VERSION}-${SHORT_SHA}'
|
||||
- 'gcr.io/$PROJECT_ID/openmatch-frontendclient:${_OM_VERSION}-${SHORT_SHA}'
|
||||
substitutions:
|
||||
_OM_VERSION: "1.1.0"
|
||||
_OM_VERSION: "0.5.0"
|
||||
_GCB_POST_SUBMIT: "0"
|
||||
_GCB_LATEST_VERSION: "undefined"
|
||||
_ARTIFACTS_BUCKET: "gs://open-match-build-artifacts/output/"
|
||||
_LOGS_BUCKET: "gs://open-match-build-logs/"
|
||||
logsBucket: '${_LOGS_BUCKET}'
|
||||
logsBucket: 'gs://open-match-build-logs/'
|
||||
options:
|
||||
sourceProvenanceHash: ['SHA256']
|
||||
machineType: 'N1_HIGHCPU_32'
|
||||
timeout: 2500s
|
||||
machineType: 'N1_HIGHCPU_8'
|
||||
# TODO: The build is slow because we don't vendor. go get takes a very long time.
|
||||
# Also we are rebuilding a lot of code unnecessarily. This should improve once
|
||||
# we have new hermetic and reproducible Dockerfiles.
|
||||
timeout: 1200s
|
||||
# TODO Build Steps
|
||||
# config/matchmaker_config.yaml: Lint this file so it's verified as a valid YAML file.
|
||||
# examples/profiles/*.json: Verify valid JSON files.
|
||||
#
|
||||
# Consolidate many of these build steps via Makefile.
|
||||
# Caching of dependencies is a serious problem. Cloud Build does not complete within 20 minutes!
|
||||
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 is the backend service for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/backend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("backend", backend.BindService)
|
||||
}
|
23
cmd/backendapi/Dockerfile
Normal file
23
cmd/backendapi/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/backendapi/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/backendapi/backendapi .
|
||||
|
||||
ENTRYPOINT ["/backendapi"]
|
14
cmd/backendapi/doc.go
Normal file
14
cmd/backendapi/doc.go
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
BackendAPI contains the unique files required to run the API endpoints for
|
||||
Open Match's backend. It is assumed you'll either integrate calls to these
|
||||
endpoints directly into your dedicated game server (simple use case), or call
|
||||
these endpoints from other, established services in your infrastructure (more
|
||||
complicated use cases).
|
||||
|
||||
Note that the main package for backendapi does very little except read the
|
||||
config and set up logging and metrics, then start the server. Almost all the
|
||||
work is being done by backendapi/apisrv, which implements the gRPC server
|
||||
defined in the backendapi/proto/backend.pb.go file.
|
||||
*/
|
||||
|
||||
package main
|
31
cmd/backendapi/main.go
Normal file
31
cmd/backendapi/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
This application handles all the startup and connection scaffolding for
|
||||
running a gRPC server serving the APIService as defined in
|
||||
${OM_ROOT}/internal/pb/backend.pb.go
|
||||
|
||||
All the actual important bits are in the API Server source code: apisrv/apisrv.go
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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
|
||||
|
||||
https://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 (
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/app/backendapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
backendapi.RunApplication()
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"open-match.dev/open-match/internal/app/evaluator/defaulteval"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("evaluator", defaulteval.BindService)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"open-match.dev/open-match/examples/demo"
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/examples/demo/components/clients"
|
||||
"open-match.dev/open-match/examples/demo/components/director"
|
||||
"open-match.dev/open-match/examples/demo/components/uptime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
demo.Run(map[string]func(*components.DemoShared){
|
||||
"uptime": uptime.Run,
|
||||
"clients": clients.Run,
|
||||
"director": director.Run,
|
||||
})
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 is the frontend service for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/frontend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("frontend", frontend.BindService)
|
||||
}
|
23
cmd/frontendapi/Dockerfile
Normal file
23
cmd/frontendapi/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/frontendapi .
|
||||
|
||||
ENTRYPOINT ["/frontendapi"]
|
14
cmd/frontendapi/doc.go
Normal file
14
cmd/frontendapi/doc.go
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
FrontendAPI contains the unique files required to run the API endpoints for
|
||||
Open Match's frontend. It is assumed you'll either integrate calls to these
|
||||
endpoints directly into your game client (simple use case), or call these
|
||||
endpoints from other, established platform services in your infrastructure
|
||||
(more complicated use cases).
|
||||
|
||||
Note that the main package for frontendapi does very little except read the
|
||||
config and set up logging and metrics, then start the server. Almost all the
|
||||
work is being done by frontendapi/apisrv, which implements the gRPC server
|
||||
defined in the frontendapi/proto/frontend.pb.go file.
|
||||
*/
|
||||
|
||||
package main
|
31
cmd/frontendapi/main.go
Normal file
31
cmd/frontendapi/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
This application handles all the startup and connection scaffolding for
|
||||
running a gRPC server serving the APIService as defined in
|
||||
${OM_ROOT}/internal/pb/frontend.pb.go
|
||||
|
||||
All the actual important bits are in the API Server source code: apisrv/apisrv.go
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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
|
||||
|
||||
https://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 (
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/app/frontendapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
frontendapi.RunApplication()
|
||||
}
|
10
cmd/minimatch/Dockerfile
Normal file
10
cmd/minimatch/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/minimatch/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/minimatch/minimatch .
|
||||
|
||||
ENTRYPOINT ["/minimatch"]
|
||||
|
@ -1,25 +1,29 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
/*
|
||||
This application is a minified version of Open Match.
|
||||
|
||||
All the actual important bits are in the API Server source code: apisrv/apisrv.go
|
||||
|
||||
Copyright 2019 Google LLC
|
||||
|
||||
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
|
||||
|
||||
https://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 is the minimatch in-process testing binary for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/minimatch"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/app/minimatch"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("minimatch", minimatch.BindService)
|
||||
minimatch.RunApplication()
|
||||
}
|
||||
|
23
cmd/mmlogicapi/Dockerfile
Normal file
23
cmd/mmlogicapi/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi/mmlogicapi .
|
||||
|
||||
ENTRYPOINT ["/mmlogicapi"]
|
30
cmd/mmlogicapi/main.go
Normal file
30
cmd/mmlogicapi/main.go
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
This application handles all the startup and connection scaffolding for
|
||||
running a gRPC server serving the APIService as defined in
|
||||
${OM_ROOT}/internal/pb/mmlogic.pb.go
|
||||
|
||||
All the actual important bits are in the API Server source code: apisrv/apisrv.go
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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
|
||||
|
||||
https://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 (
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/app/mmlogicapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mmlogicapi.RunApplication()
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 is the query service for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/query"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("query", query.BindService)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"open-match.dev/open-match/examples/scale/backend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("scale", backend.BindService)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// 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 (
|
||||
scaleEvaluator "open-match.dev/open-match/examples/scale/evaluator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
scaleEvaluator.Run()
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"open-match.dev/open-match/examples/scale/frontend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("scale", frontend.BindService)
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
scaleMmf "open-match.dev/open-match/examples/scale/mmf"
|
||||
)
|
||||
|
||||
func main() {
|
||||
scaleMmf.Run()
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"urls": [
|
||||
{"name": "Frontend", "url": "https://open-match.dev/api/v0.0.0-dev/frontend.swagger.json"},
|
||||
{"name": "Backend", "url": "https://open-match.dev/api/v0.0.0-dev/backend.swagger.json"},
|
||||
{"name": "Query", "url": "https://open-match.dev/api/v0.0.0-dev/query.swagger.json"},
|
||||
{"name": "MatchFunction", "url": "https://open-match.dev/api/v0.0.0-dev/matchfunction.swagger.json"},
|
||||
{"name": "Synchronizer", "url": "https://open-match.dev/api/v0.0.0-dev/synchronizer.swagger.json"},
|
||||
{"name": "Evaluator", "url": "https://open-match.dev/api/v0.0.0-dev/evaluator.swagger.json"}
|
||||
]
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 is a simple webserver for hosting Open Match Swagger UI.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/swaggerui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
swaggerui.RunApplication()
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 is the synchronizer service for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/synchronizer"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("synchronizer", synchronizer.BindService)
|
||||
}
|
132
config/config.go
Normal file
132
config/config.go
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
Package config contains convenience functions for reading and managing viper configs.
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
logFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "config",
|
||||
}
|
||||
cfgLog = log.WithFields(logFields)
|
||||
|
||||
// Map of the config file keys to environment variable names populated by
|
||||
// k8s into pods. Examples of redis-related env vars as written by k8s
|
||||
// REDIS_SENTINEL_PORT_6379_TCP=tcp://10.55.253.195:6379
|
||||
// REDIS_SENTINEL_PORT=tcp://10.55.253.195:6379
|
||||
// REDIS_SENTINEL_PORT_6379_TCP_ADDR=10.55.253.195
|
||||
// REDIS_SENTINEL_SERVICE_PORT=6379
|
||||
// REDIS_SENTINEL_PORT_6379_TCP_PORT=6379
|
||||
// REDIS_SENTINEL_PORT_6379_TCP_PROTO=tcp
|
||||
// REDIS_SENTINEL_SERVICE_HOST=10.55.253.195
|
||||
//
|
||||
// MMFs are expected to get their configuation from env vars instead
|
||||
// of reading the config file. So, config parameters that are required
|
||||
// by MMFs should be populated to env vars.
|
||||
envMappings = map[string]string{
|
||||
"redis.user": "REDIS_USER",
|
||||
"redis.password": "REDIS_PASSWORD",
|
||||
"redis.hostname": "REDIS_SERVICE_HOST",
|
||||
"redis.port": "REDIS_SERVICE_PORT",
|
||||
"redis.pool.maxIdle": "REDIS_POOL_MAXIDLE",
|
||||
"redis.pool.maxActive": "REDIS_POOL_MAXACTIVE",
|
||||
"redis.pool.idleTimeout": "REDIS_POOL_IDLETIMEOUT",
|
||||
"api.mmlogic.hostname": "OM_MMLOGICAPI_SERVICE_HOST",
|
||||
"api.mmlogic.port": "OM_MMLOGICAPI_SERVICE_PORT",
|
||||
}
|
||||
|
||||
// OpenCensus
|
||||
cfgVarCount = stats.Int64("config/vars_total", "Number of config vars read during initialization", "1")
|
||||
// CfgVarCountView is the Open Census view for the cfgVarCount measure.
|
||||
CfgVarCountView = &view.View{
|
||||
Name: "config/vars_total",
|
||||
Measure: cfgVarCount,
|
||||
Description: "The number of config vars read during initialization",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// Read reads a config file into a viper.Viper instance and associates environment vars defined in
|
||||
// config.envMappings
|
||||
func Read() (View, error) {
|
||||
cfg := viper.New()
|
||||
// Viper config management initialization
|
||||
// Support either json or yaml file types (json for backwards compatibility
|
||||
// with previous versions)
|
||||
cfg.SetConfigType("json")
|
||||
cfg.SetConfigType("yaml")
|
||||
cfg.SetConfigName("matchmaker_config")
|
||||
cfg.AddConfigPath(".")
|
||||
cfg.AddConfigPath("config")
|
||||
|
||||
// Read in config file using Viper
|
||||
err := cfg.ReadInConfig()
|
||||
if err != nil {
|
||||
cfgLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatal("Fatal error reading config file")
|
||||
}
|
||||
|
||||
// Bind this envvars to viper config vars.
|
||||
// https://github.com/spf13/viper#working-with-environment-variables
|
||||
// One important thing to recognize when working with ENV variables is
|
||||
// that the value will be read each time it is accessed. Viper does not
|
||||
// fix the value when the BindEnv is called.
|
||||
for cfgKey, envVar := range envMappings {
|
||||
err = cfg.BindEnv(cfgKey, envVar)
|
||||
|
||||
if err != nil {
|
||||
cfgLog.WithFields(log.Fields{
|
||||
"configkey": cfgKey,
|
||||
"envvar": envVar,
|
||||
"error": err.Error(),
|
||||
"module": "config",
|
||||
}).Warn("Unable to bind environment var as a config variable")
|
||||
|
||||
} else {
|
||||
cfgLog.WithFields(log.Fields{
|
||||
"configkey": cfgKey,
|
||||
"envvar": envVar,
|
||||
"module": "config",
|
||||
}).Info("Binding environment var as a config variable")
|
||||
}
|
||||
}
|
||||
|
||||
// Look for updates to the config; in Kubernetes, this is implemented using
|
||||
// a ConfigMap that is written to the matchmaker_config.yaml file, which is
|
||||
// what the Open Match components using Viper monitor for changes.
|
||||
// More details about Open Match's use of Kubernetes ConfigMaps at:
|
||||
// https://github.com/GoogleCloudPlatform/open-match/issues/42
|
||||
cfg.WatchConfig() // Watch and re-read config file.
|
||||
// Write a log when the configuration changes.
|
||||
cfg.OnConfigChange(func(event fsnotify.Event) {
|
||||
cfgLog.WithFields(log.Fields{
|
||||
"filename": event.Name,
|
||||
"operation": event.Op,
|
||||
}).Info("Server configuration changed.")
|
||||
})
|
||||
return cfg, err
|
||||
}
|
33
config/config_test.go
Normal file
33
config/config_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Package config contains convenience functions for reading and managing configuration.
|
||||
|
||||
Copyright 2019 Google LLC
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
cfg, err := Read()
|
||||
if err != nil {
|
||||
t.Fatalf("cannot load config, %s", err)
|
||||
}
|
||||
|
||||
if cfg.GetString("metrics.endpoint") != "/metrics" {
|
||||
t.Errorf("av.GetString('metrics.endpoint') = %s, expected '/metrics'", cfg.GetString("metrics.endpoint"))
|
||||
}
|
||||
}
|
90
config/matchmaker_config.yaml
Normal file
90
config/matchmaker_config.yaml
Normal file
@ -0,0 +1,90 @@
|
||||
# kubectl create configmap om-configmap --from-file=config/matchmaker_config.yaml
|
||||
debug: true
|
||||
|
||||
logging:
|
||||
level: debug
|
||||
format: text
|
||||
source: false
|
||||
|
||||
api:
|
||||
backend:
|
||||
hostname: om-backendapi
|
||||
port: 50505
|
||||
backoff: "[2 32] *2 ~0.33 <30"
|
||||
proxyport: 51505
|
||||
frontend:
|
||||
hostname: om-frontendapi
|
||||
port: 50504
|
||||
backoff: "[2 32] *2 ~0.33 <300"
|
||||
proxyport: 51504
|
||||
mmlogic:
|
||||
hostname: om-mmlogicapi
|
||||
port: 50503
|
||||
proxyport: 51503
|
||||
functions:
|
||||
port: 50502
|
||||
proxyport: 51502
|
||||
|
||||
evaluator:
|
||||
# Evaluator intervals are in milliseconds
|
||||
pollIntervalMs: 1000
|
||||
maxWaitMs: 10000
|
||||
|
||||
metrics:
|
||||
port: 9555
|
||||
endpoint: /metrics
|
||||
reportingPeriod: 5
|
||||
|
||||
queues:
|
||||
proposals:
|
||||
name: proposalq
|
||||
|
||||
ignoreLists:
|
||||
proposed:
|
||||
name: proposed
|
||||
offset: 0
|
||||
duration: 800
|
||||
deindexed:
|
||||
name: deindexed
|
||||
offset: 0
|
||||
duration: 800
|
||||
expired:
|
||||
name: OM_METADATA.accessed
|
||||
offset: 800
|
||||
duration: 0
|
||||
|
||||
redis:
|
||||
pool:
|
||||
maxIdle: 3
|
||||
maxActive: 0
|
||||
idleTimeout: 60
|
||||
queryArgs:
|
||||
count: 10000
|
||||
results:
|
||||
pageSize: 10000
|
||||
expirations:
|
||||
player: 43200
|
||||
matchobject: 43200
|
||||
|
||||
jsonkeys:
|
||||
rosters: properties.rosters
|
||||
pools: properties.pools
|
||||
|
||||
playerIndices:
|
||||
- char.cleric
|
||||
- char.knight
|
||||
- char.paladin
|
||||
- map.aleroth
|
||||
- map.oasis
|
||||
- mmr.rating
|
||||
- mode.battleroyale
|
||||
- mode.ctf
|
||||
- mode.demo
|
||||
- region.europe-east1
|
||||
- region.europe-west1
|
||||
- region.europe-west2
|
||||
- region.europe-west3
|
||||
- region.europe-west4
|
||||
- role.dps
|
||||
- role.support
|
||||
- role.tank
|
46
config/view.go
Normal file
46
config/view.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Package config contains convenience functions for reading and managing configuration.
|
||||
|
||||
Copyright 2019 Google LLC
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// View is a read-only view of the Open Match configuration.
|
||||
// New accessors from Viper should be added here.
|
||||
type View interface {
|
||||
IsSet(string) bool
|
||||
GetString(string) string
|
||||
GetInt(string) int
|
||||
GetInt64(string) int64
|
||||
GetStringSlice(string) []string
|
||||
GetBool(string) bool
|
||||
GetDuration(string) time.Duration
|
||||
GetStringMap(string) map[string]interface{}
|
||||
}
|
||||
|
||||
// Sub returns a subset of configuration filtered by the key.
|
||||
func Sub(v View, key string) View {
|
||||
vcfg, ok := v.(*viper.Viper)
|
||||
if ok {
|
||||
return vcfg.Sub(key)
|
||||
}
|
||||
return nil
|
||||
}
|
50
config/view_test.go
Normal file
50
config/view_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Package config contains convenience functions for reading and managing configuration.
|
||||
|
||||
Copyright 2019 Google LLC
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func TestSubFromViper(t *testing.T) {
|
||||
v := viper.New()
|
||||
v.Set("a", "a")
|
||||
v.Set("b", "b")
|
||||
v.Set("c", "c")
|
||||
v.Set("a.a", "a.a")
|
||||
v.Set("a.b", "a.b")
|
||||
av := Sub(v, "a")
|
||||
if av == nil {
|
||||
t.Fatalf("Sub(%v, 'a') => %v", v, av)
|
||||
}
|
||||
|
||||
if av.GetString("a") != "a.a" {
|
||||
t.Errorf("av.GetString('a') = %s, expected 'a.a'", av.GetString("a"))
|
||||
}
|
||||
if av.GetString("a.a") != "" {
|
||||
t.Errorf("av.GetString('a.a') = %s, expected ''", av.GetString("a.a"))
|
||||
}
|
||||
if av.GetString("b") != "a.b" {
|
||||
t.Errorf("av.GetString('b') = %s, expected 'a.b'", av.GetString("b"))
|
||||
}
|
||||
if av.GetString("c") != "" {
|
||||
t.Errorf("av.GetString('c') = %s, expected ''", av.GetString(""))
|
||||
}
|
||||
}
|
30
doc.go
30
doc.go
@ -1,16 +1,18 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
/*
|
||||
* Copyright 2019 Google Inc. 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 openmatch provides flexible, extensible, and scalable video game matchmaking.
|
||||
package openmatch // import "open-match.dev/open-match"
|
||||
package openmatch
|
||||
|
114
docs/concepts.md
Normal file
114
docs/concepts.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Core Concepts
|
||||
|
||||
[Watch the introduction of Open Match at Unite Berlin 2018 on YouTube](https://youtu.be/qasAmy_ko2o)
|
||||
|
||||
Open Match is designed to support massively concurrent matchmaking, and to be scalable to player populations of hundreds of millions or more. It attempts to apply stateless web tech microservices patterns to game matchmaking. If you're not sure what that means, that's okay — it is fully open source and designed to be customizable to fit into your online game architecture — so have a look a the code and modify it as you see fit.
|
||||
|
||||
## Glossary
|
||||
|
||||
### General
|
||||
|
||||
* **DGS** — Dedicated game server
|
||||
* **Client** — The game client program the player uses when playing the game
|
||||
* **Session** — In Open Match, players are matched together, then assigned to a server which hosts the game _session_. Depending on context, this may be referred to as a _match_, _map_, or just _game_ elsewhere in the industry.
|
||||
|
||||
### Open Match
|
||||
|
||||
* **Component** — One of the discrete processes in an Open Match deployment. Open Match is composed of multiple scalable microservices called _components_.
|
||||
* **State Storage** — The storage software used by Open Match to hold all the matchmaking state. Open Match ships with [Redis](https://redis.io/) as the default state storage.
|
||||
* **MMF** — Matchmaking function. This is the customizable matchmaking logic.
|
||||
* **Function Harness** — A GRPC serving harness that triggers the Match function.
|
||||
* **Evaluator** — Customizable evaluation logic that analyzes match proposals and approves / rejects matches.
|
||||
* **MMLogic API** — An API that provides MMF SDK functionality.
|
||||
* **Director** — The software you (as a developer) write against the Open Match Backend API. The _Director_ decides which MMFs to run, and is responsible for sending MMF results to a DGS to host the session.
|
||||
|
||||
### Data Model
|
||||
|
||||
* **Player** — An ID and list of attributes with values for a player who wants to participate in matchmaking.
|
||||
* **Roster** — A list of player objects. Used to hold all the players on a single team.
|
||||
* **Filter** — A _filter_ is used to narrow down the players to only those who have an attribute value within a certain integer range. All attributes are integer values in Open Match because [that is how indices are implemented](internal/statestorage/redis/playerindices/playerindices.go). A _filter_ is defined in a _player pool_.
|
||||
* **Player Pool** — A list of all the players who fit all the _filters_ defined in the pool.
|
||||
* **Match Object** — A protobuffer message format that contains the _profile_ and the results of the matchmaking function. Sent to the backend API from your game backend with the _roster_(s) empty and then returned from your MMF with the matchmaking results filled in.
|
||||
* **Profile** — The json blob containing all the parameters used by your MMF to select which players go into a roster together.
|
||||
* **Assignment** — Refers to assigning a player or group of players to a dedicated game server instance. Open Match offers a path to send dedicated game server connection details from your backend to your game clients after a match has been made.
|
||||
* **Ignore List** — Removing players from matchmaking consideration is accomplished using _ignore lists_. They contain lists of player IDs that your MMF should not include when making matches.
|
||||
|
||||
## Requirements
|
||||
|
||||
* [Kubernetes](https://kubernetes.io/) cluster — tested with version 1.11.7.
|
||||
* [Redis 4+](https://redis.io/) — tested with 4.0.11.
|
||||
* Open Match is compiled against the latest release of [Golang](https://golang.org/) — tested with 1.11.5.
|
||||
|
||||
## Components
|
||||
|
||||
Open Match is a set of processes designed to run on Kubernetes. It contains these **core** components:
|
||||
|
||||
* Frontend API
|
||||
* Backend API
|
||||
* Matchmaking Logic (MMLogic) API
|
||||
|
||||
It also depends on these two **customizable** components.
|
||||
|
||||
* Match Function (MMF)
|
||||
* Evaluator
|
||||
|
||||
While **core** components are fully open source and _can_ be modified, they are designed to support the majority of matchmaking scenarios *without need to change the source code*. The Open Match repository ships with simple **customizable** MMF and Evaluator examples, but it is expected that most users will want full control over the logic in these, so they have been designed to be as easy to modify or replace as possible.
|
||||
|
||||
### Frontend API
|
||||
|
||||
The Frontend API accepts the player data and puts it in state storage so your Matchmaking Function (MMF) can access it.
|
||||
|
||||
The Frontend API is a server application that implements the [gRPC](https://grpc.io/) service defined in `api/protobuf-spec/frontend.proto`. At the most basic level, it expects clients to connect and send:
|
||||
* A **unique ID** for the group of players (the group can contain any number of players, including only one).
|
||||
* A **json blob** containing all player-related data you want to use in your matchmaking function.
|
||||
|
||||
The client is expected to maintain a connection, waiting for an update from the API that contains the details required to connect to a dedicated game server instance (an 'assignment'). There are also basic functions for removing an ID from the matchmaking pool or an existing match.
|
||||
|
||||
### Backend API
|
||||
|
||||
The Backend API writes match objects to state storage which the Matchmaking Functions (MMFs) access to decide which players should be matched. It returns the results from those MMFs.
|
||||
|
||||
The Backend API is a server application that implements the [gRPC](https://grpc.io/) service defined in `api/protobuf-spec/backend.proto`. At the most basic level, it expects to be connected to your online infrastructure (probably to your server scaling manager or **director**, or even directly to a dedicated game server), and to receive:
|
||||
* A **unique ID** for a matchmaking profile.
|
||||
* A **json blob** containing all the matching-related data and filters you want to use in your matchmaking function.
|
||||
* An optional list of **roster**s to hold the resulting teams chosen by your matchmaking function.
|
||||
* An optional set of **filters** that define player pools your matchmaking function will choose players from.
|
||||
|
||||
Your game backend is expected to maintain a connection, waiting for 'filled' match objects containing a roster of players. The Backend API also provides a return path for your game backend to return dedicated game server connection details (an 'assignment') to the game client, and to delete these 'assignments'.
|
||||
|
||||
### Matchmaking Logic (MMLogic) API
|
||||
|
||||
The MMLogic API provides a series of gRPC functions that act as a Matchmaking Function SDK. Much of the basic, boilerplate code for an MMF is the same regardless of what players you want to match together. The MMLogic API offers a gRPC interface for many common MMF tasks, such as:
|
||||
|
||||
1. Reading a profile from state storage.
|
||||
1. Running filters on players in state strorage. It automatically removes players on ignore lists as well!
|
||||
1. Removing chosen players from consideration by other MMFs (by adding them to an ignore list). It does it automatically for you when writing your results!
|
||||
1. Writing the matchmaking results to state storage.
|
||||
1. (Optional, NYI) Exporting MMF stats for metrics collection.
|
||||
|
||||
More details about the available gRPC calls can be found in the [API Specification](api/protobuf-spec/messages.proto).
|
||||
|
||||
**Note**: using the MMLogic API is **optional**. It tries to simplify the development of MMFs, but if you want to take care of these tasks on your own, you can make few or no calls to the MMLogic API as long as your MMF still completes all the required tasks. Read the [Matchmaking Functions section](#matchmaking-functions-mmfs) for more details of what work an MMF must do.
|
||||
|
||||
### Evaluator
|
||||
|
||||
The Evaluator resolves conflicts when multiple MMFs select the same player(s). Evaluator is provided by the developer (sample included in Open Match).
|
||||
|
||||
The Evaluator runs forever, looping over a configured interval, checking if MMFs have completed execution or if certain time interval has passed. Upon reaching those conditions, the Evaluator calls the Evaluation Function (to be modified by the user) with the proposals to choose from. The sample Evaluation function looks at all the proposals, and if multiple proposals contain the same player(s), it breaks the tie. In many simple matchmaking setups with only a few game modes and well-tuned matchmaking functions, the Evaluator may functionally be a no-op or first-in-first-out algorithm. In complex matchmaking setups where, for example, a player can queue for multiple types of matches, the Evaluator provides the critical customizability to evaluate all available proposals and approve those that will passed to your game servers.
|
||||
|
||||
Large-scale concurrent matchmaking functions is a complex topic, and users who wish to do this are encouraged to engage with the [Open Match community](https://github.com/GoogleCloudPlatform/open-match#get-involved) about patterns and best practices.
|
||||
|
||||
### Matchmaking Functions (MMFs)
|
||||
|
||||
Matchmaking Functions (MMFs) are implemented by the developer and are hosted as a gRPC service. Open Match provides a harness (currently for golang) that handles the broiler-plate Open Match communitation, gRPC server setup etc., so that the user only has to write a function that accepts a set of player pools and a match profile and returns a proposal based on some core match making logic. An MMF is called each time a request to generate a match is received. At a high level, an MMF needs to generate a proposal using the given players, match profile and its custom match making logic and return the proposal to the calling harness.
|
||||
**Note**: Currently Open Match only has a golang harness. To add an MMF in any other language, a harness needs to be implemented in that language.
|
||||
|
||||
## Example Tooling
|
||||
|
||||
To see Open Match, in action, here are some basic tools that are provided as samples:
|
||||
|
||||
* `test/cmd/clientloadgen/` is a (VERY) basic client load simulation tool. It endlessly writes players into state storage so you can test your backend integration, and run your custom MMFs and Evaluators (which are only triggered when there are players in the pool).
|
||||
|
||||
* `examples/backendclient` is a fake client for the Backend API. It pretends to be a dedicated game server backend connecting to Open Match and sending in a match profile to fill and receives completed matches. It can call Create / List matches.
|
||||
|
||||
* `test/cmd/frontendclient/` is a fake client for the Frontend API. It pretends to be group of real game clients connecting to Open Match. It requests a game, then dumps out the results each player receives to the screen.
|
@ -1,162 +1,175 @@
|
||||
# Development Guide
|
||||
|
||||
Open Match is a collection of [Go](https://golang.org/) gRPC services that run
|
||||
within [Kubernetes](https://kubernetes.io).
|
||||
This doc explains how to setup a development environment, compile from source and deploy your changes to test cluster. This document is targeted to to developers contributing to Open Match.
|
||||
|
||||
## Install Prerequisites
|
||||
## Security Disclaimer
|
||||
**This project has not completed a first-line security audit. This should be fine for testing/development in a local environment, but absolutely should not be used as-is in a production environment.**
|
||||
|
||||
To build Open Match you'll need the following applications installed.
|
||||
## Setting up a local Open Match Repository
|
||||
|
||||
* [Git](https://git-scm.com/downloads)
|
||||
* [Go](https://golang.org/doc/install)
|
||||
* Make (Mac: install [XCode](https://itunes.apple.com/us/app/xcode/id497799835))
|
||||
* [Docker](https://docs.docker.com/install/) including the
|
||||
[post-install steps](https://docs.docker.com/install/linux/linux-postinstall/).
|
||||
|
||||
Optional Software
|
||||
|
||||
* [Visual Studio Code](https://code.visualstudio.com/Download) for IDE.
|
||||
Vim and Emacs work to.
|
||||
* [VirtualBox](https://www.virtualbox.org/wiki/Downloads) recommended for
|
||||
[Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/).
|
||||
|
||||
On Debian-based Linux you can install all the required packages (except Go) by
|
||||
running:
|
||||
Here are the instructions to set up a local repository for Open Match.
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -q make google-cloud-sdk git unzip tar
|
||||
```
|
||||
|
||||
*It's recommended that you install Go using their instructions because package
|
||||
managers tend to lag behind the latest Go releases.*
|
||||
|
||||
## Get the Code
|
||||
|
||||
```bash
|
||||
# Create a directory for the project.
|
||||
mkdir -p $HOME/workspace
|
||||
cd $HOME/workspace
|
||||
# Download the source code.
|
||||
git clone https://github.com/googleforgames/open-match.git
|
||||
# Install Open Match Toolchain Dependencies (for Debian, other OSes including Mac OS X have similar dependencies)
|
||||
sudo apt-get update; sudo apt-get install -y -q python3 python3-virtualenv virtualenv make google-cloud-sdk git unzip tar
|
||||
mkdir -p $HOME/<workspace>
|
||||
cd $HOME/<workspace>
|
||||
git clone https://github.com/GoogleCloudPlatform/open-match.git
|
||||
cd open-match
|
||||
# Print the help for the Makefile commands.
|
||||
make
|
||||
```
|
||||
|
||||
*Typically for contributing you'll want to
|
||||
[create a fork](https://help.github.com/en/articles/fork-a-repo) and use that
|
||||
but for purpose of this guide we'll be using the upstream/master.*
|
||||
## Compiling From Source
|
||||
|
||||
## Building code and images
|
||||
The easiest way to build Open Match is to use the [Makefile](Makefile). This section assumes that you have followed the steps to [Setup Local Open Match Repository](#local-repository-setup).
|
||||
|
||||
You will also need [Docker](https://docs.docker.com/install/) and [Go 1.12+](https://golang.org/dl/) installed.
|
||||
|
||||
To build all the artifacts of Open Match, please run the following commands:
|
||||
|
||||
```bash
|
||||
# Reset workspace
|
||||
make clean
|
||||
# Run tests
|
||||
make test
|
||||
# Build all the images.
|
||||
make build-images -j$(nproc)
|
||||
# Push images to gcr.io (requires Google Cloud SDK installed)
|
||||
make push-images -j$(nproc)
|
||||
# Push images to Docker Hub
|
||||
make REGISTRY=mydockerusername push-images -j$(nproc)
|
||||
# Generate Kubernetes installation YAML files (Note that the trailing '/' is needed here)
|
||||
make install/yaml/
|
||||
# Downloads all the tools needed to build Open Match
|
||||
make install-toolchain
|
||||
# Generates protocol buffer code files
|
||||
make all-protos
|
||||
# Builds all the binaries
|
||||
make all
|
||||
# Builds all the images.
|
||||
make build-images
|
||||
```
|
||||
|
||||
_**-j$(nproc)** is a flag to tell make to parallelize the commands based on
|
||||
the number of CPUs on your machine._
|
||||
After successfully building, run `docker images` to see all the images that were build.
|
||||
|
||||
## Deploying to Kubernetes
|
||||
Before creating a pull request you can run `make local-cloud-build` to simulate a Cloud Build run to check for regressions.
|
||||
|
||||
Kubernetes comes in many flavors and Open Match can be used in any of them.
|
||||
The [Build Queue](https://console.cloud.google.com/cloud-build/builds?project=open-match-build) runs against all PRs, requires membership to [open-match-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/open-match-discuss).
|
||||
|
||||
_We support GKE ([setup guide](gcloud.md)), Minikube, and Kubernetes in Docker (KinD) in the Makefile.
|
||||
As long as kubectl is configured to talk to your Kubernetes cluster as the
|
||||
default context the Makefile will honor that._
|
||||
## Deploy Open Match to Google Cloud Platform
|
||||
|
||||
Create a GCP project via [Google Cloud Console](https://console.cloud.google.com/). Billing must be enabled but if you're a new customer you can get some [free credits](https://cloud.google.com/free/). When you create a project you'll need to set a Project ID, if you forget it you can see it here, https://console.cloud.google.com/iam-admin/settings/project.
|
||||
|
||||
Now install [Google Cloud SDK](https://cloud.google.com/sdk/) which is the command line tool to work against your project. The following commands log you into your GCP Project.
|
||||
|
||||
```bash
|
||||
# Step 1: Create a Kubernetes (k8s) cluster
|
||||
# KinD cluster: make create-kind-cluster/delete-kind-cluster
|
||||
# GKE cluster: make create-gke-cluster/delete-gke-cluster
|
||||
# or create a local Minikube cluster
|
||||
make create-gke-cluster
|
||||
# Step 2: Build and Push Open Match Images to gcr.io
|
||||
make push-images -j$(nproc)
|
||||
# Step 3: Install Open Match in the cluster.
|
||||
# Login to your Google Account for GCP.
|
||||
gcloud auth login
|
||||
gcloud config set project $YOUR_GCP_PROJECT_ID
|
||||
# Enable GCP services
|
||||
gcloud services enable containerregistry.googleapis.com
|
||||
gcloud services enable container.googleapis.com
|
||||
# Test that everything is good, this command should work.
|
||||
gcloud compute zones list
|
||||
```
|
||||
|
||||
This section assumes that you have followed the steps to [Setup Local Open Match Repository](#local-repository-setup). Once everything is setup you can deploy Open Match by creating a cluster in Google Kubernetes Engine (GKE).
|
||||
|
||||
```bash
|
||||
# Create a GKE Cluster and install Helm
|
||||
make create-gke-cluster push-helm
|
||||
# Push images to Registry
|
||||
make push-images
|
||||
# Deploy Open Match
|
||||
make install-chart
|
||||
|
||||
# Create a proxy to Open Match pods so that you can access them locally.
|
||||
# This command consumes a terminal window that you can kill via Ctrl+C.
|
||||
# You can run `curl -X POST http://localhost:51504/v1/frontend/tickets` to send
|
||||
# a DeleteTicket request to the frontend service in the cluster.
|
||||
# Then try visiting http://localhost:3000/ and view the graphs.
|
||||
make proxy
|
||||
|
||||
# Teardown the install
|
||||
make delete-chart
|
||||
```
|
||||
|
||||
## Iterating
|
||||
While iterating on the project, you may need to:
|
||||
1. Install/Run everything
|
||||
2. Make some code changes
|
||||
3. Make sure the changes compile by running `make test`
|
||||
4. Build and push Docker images to your personal registry by running `make push-images -j$(nproc)`
|
||||
5. Deploy the code change by running `make install-chart`
|
||||
6. Verify it's working by [looking at the logs](#accessing-logs) or looking at the monitoring dashboard by running `make proxy-grafana`
|
||||
7. Tear down Open Match by running `make delete-chart`
|
||||
This will install all Open Match core components to the kubernetes cluster. Once deployed you can view the jobs in [Cloud Console](https://console.cloud.google.com/kubernetes/workload).
|
||||
|
||||
## Accessing logs
|
||||
To look at Open Match core services' logs, run:
|
||||
```bash
|
||||
# Replace open-match-frontend with the service name that you would like to access
|
||||
kubectl logs -n open-match svc/open-match-frontend
|
||||
Run `kubectl --namespace open-match get pods,svc` to verify if the deployment succeded. If everything started correctly, the output should look like:
|
||||
|
||||
```
|
||||
$ kubectl --namespace open-match get pods,svc
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/om-backendapi-6f8f9796f7-ncfgf 1/1 Running 0 10m
|
||||
pod/om-frontendapi-868f7df859-5dbcd 1/1 Running 0 10m
|
||||
pod/om-mmlogicapi-5998dcdc9c-vmjhn 1/1 Running 0 10m
|
||||
pod/om-redis-master-0 1/1 Running 0 10m
|
||||
pod/om-redis-metrics-66c8fbfbc-vnmls 1/1 Running 0 10m
|
||||
pod/om-redis-slave-8477c666fc-kb2gv 1/1 Running 1 10m
|
||||
pod/open-match-grafana-6769f969f-t76zz 2/2 Running 0 10m
|
||||
pod/open-match-prometheus-alertmanager-58c9f6ffc7-7f7fq 2/2 Running 0 10m
|
||||
pod/open-match-prometheus-kube-state-metrics-79c8d85c55-q69qf 1/1 Running 0 10m
|
||||
pod/open-match-prometheus-node-exporter-88pjh 1/1 Running 0 10m
|
||||
pod/open-match-prometheus-node-exporter-qq9h7 1/1 Running 0 10m
|
||||
pod/open-match-prometheus-node-exporter-rcmdq 1/1 Running 0 10m
|
||||
pod/open-match-prometheus-pushgateway-6c67d47f48-8bhgk 1/1 Running 0 10m
|
||||
pod/open-match-prometheus-server-86c459ddc4-gk5m7 2/2 Running 0 10m
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/om-backendapi ClusterIP 10.0.2.206 <none> 50505/TCP,51505/TCP 10m
|
||||
service/om-frontendapi ClusterIP 10.0.14.157 <none> 50504/TCP,51504/TCP 10m
|
||||
service/om-mmlogicapi ClusterIP 10.0.10.71 <none> 50503/TCP,51503/TCP 10m
|
||||
service/om-redis-master ClusterIP 10.0.9.110 <none> 6379/TCP 10m
|
||||
service/om-redis-metrics ClusterIP 10.0.9.114 <none> 9121/TCP 10m
|
||||
service/om-redis-slave ClusterIP 10.0.3.46 <none> 6379/TCP 10m
|
||||
service/open-match-grafana ClusterIP 10.0.0.213 <none> 3000/TCP 10m
|
||||
service/open-match-prometheus-alertmanager ClusterIP 10.0.6.126 <none> 80/TCP 10m
|
||||
service/open-match-prometheus-kube-state-metrics ClusterIP None <none> 80/TCP 10m
|
||||
service/open-match-prometheus-node-exporter ClusterIP None <none> 9100/TCP 10m
|
||||
service/open-match-prometheus-pushgateway ClusterIP 10.0.15.222 <none> 9091/TCP 10m
|
||||
service/open-match-prometheus-server ClusterIP 10.0.6.7 <none> 80/TCP 10m
|
||||
```
|
||||
|
||||
## API References
|
||||
While integrating with Open Match you may want to understand its API surface concepts or interact with it and get a feel for how it works.
|
||||
## End-to-End testing
|
||||
|
||||
The APIs are defined in `proto` format under the `api/` folder, with references available at [open-match.dev](https://open-match.dev/site/docs/reference/api/).
|
||||
### Example MMF, Evaluator
|
||||
|
||||
You can also run `make proxy-ui` to exposes the Swagger UI for Open Match locally on your computer after [deploying it to Kubernetes](#deploying-to-kubernetes), then go to http://localhost:51500 and view the REST APIs as well as interactively call Open Match.
|
||||
When Open Match is setup, it requires a Match Function and an Evaluator to be set up that it will call into at runtime when requests to generate matches are received. Open Match itself provides harness code (currently only for golang) that abstracts the complexity of setting up the Match Function and Evaluator as GRPC services. You do not need to modify the harness code but simply the actual Match Function, Evaluator Function to suit your game's needs. Open Match includes sample Match function and Evaluation Function as described below:
|
||||
|
||||
By default you will be talking to the frontend server but you can change the target API url to any of the following:
|
||||
* `examples/functions/golang/grpc-serving` is a sample Match function that is built using the GRPC harness. The function scans a simple profile, populating a player into each Roster slot that matches the requested player pool. This function is over-simplified simply matching player pools to roster slots. You will need to modify this function to add your match making logic.
|
||||
|
||||
* api/frontend.swagger.json
|
||||
* api/backend.swagger.json
|
||||
* api/synchronizer.swagger.json
|
||||
* api/query.swagger.json
|
||||
* `examples/evaluators/golang/serving` is a sample evaluator function that is called by an evaluator harness that runs as forever-runnig kubernetes job. The function is triggered each time there are results to evaluate. The current sample simply approves matches with unique players, identifies the ones with overlap and approves the first overlapping player match rejecting the rest. You would need to build your own evaluation logic with this sample as a reference.
|
||||
|
||||
For a more current list refer to the api/ directory of this repository. Also matchfunction.swagger.json is not supported.
|
||||
### Example Tooling
|
||||
|
||||
## IDE Support
|
||||
Once Open Match core components are set up and your Match Function and Evaluator GRPC services are running, Open Match functionality is triggered when new players request assignments and when the game backend requests matches. To see Open Match, in action, here are some basic tools that are provided as samples. Note that these tools are meant to exercise Open Match functionality and should only be used as a reference point when building similar abilities into your components using Open Match.
|
||||
|
||||
Open Match is a standard Go project so any IDE that understands that should
|
||||
work. We use [Go Modules](https://github.com/golang/go/wiki/Modules) which is a
|
||||
relatively new feature in Go so make sure the IDE you are using was built around
|
||||
Summer 2019. The latest version of
|
||||
[Visual Studio Code](https://code.visualstudio.com/download) supports it.
|
||||
* `test/cmd/clientloadgen/` is a (VERY) basic client load simulation tool. It does **not** test the Frontend API - in fact, it ignores it and writes players directly to state storage on its own. It doesn't do anything but loop endlessly, writing players into state storage so you can test your backend integration, and run your custom MMFs and Evaluators (which are only triggered when there are players in the pool).
|
||||
|
||||
If your IDE is too old you can create a
|
||||
[Go workspace](https://golang.org/doc/code.html#Workspaces).
|
||||
* `examples/backendclient` is a fake client for the Backend API. It pretends to be a dedicated game server backend connecting to Open Match and sending in a match profile to fill. Once it receives a match object with a roster, it will also issue a call to assign the player IDs, and gives an example connection string. If it never seems to get a match, make sure you're adding players to the pool using the other two tools. **Note**: If you run this by itself, expect it to wait about 30 seconds, then return a result of 'insufficient players' and exit - this is working as intended. Use the client load simulation tool below to add players to the pool or you'll never be able to make a successful match.
|
||||
|
||||
* `test/cmd/frontendclient/` is a fake client for the Frontend API. It pretends to be group of real game clients connecting to Open Match. It requests a game, then dumps out the results each player receives to the screen until you press the enter key. **Note**: If you're using the rest of these test programs, you're probably using the Backend Client below. The default profiles that command sends to the backend look for many more than one player, so if you want to see meaningful results from running this Frontend Client, you're going to need to generate a bunch of fake players using the client load simulation tool at the same time. Otherwise, expect to wait until it times out as your matchmaker never has enough players to make a successful match. Also, if the simulator has generated significant load, the player injected by te Frontend Client may still not find a match by the timeout duration and exit.
|
||||
|
||||
### Setting up an E2E scenario
|
||||
|
||||
These steps assume that you already have [deployed core Open Match to a cluster](deploy-open-match-to-google-cloud-platform). Once Open Match is deployed, run the below command to deploy the Match Function Harness, the Evaluator and the Client Load Generator to the Open Match cluster.
|
||||
|
||||
```bash
|
||||
# Create the Go workspace in $HOME/workspace/ directory.
|
||||
mkdir -p $HOME/workspace/src/open-match.dev/
|
||||
cd $HOME/workspace/src/open-match.dev/
|
||||
# Download the source code.
|
||||
git clone https://github.com/googleforgames/open-match.git
|
||||
cd open-match
|
||||
export GOPATH=$HOME/workspace/
|
||||
# Deploy Open Match
|
||||
make install-example-chart
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
Once this succeeds, run the below command to validate that these components are up and running as expected (in addition to the Open Match core components):
|
||||
|
||||
If you want to submit a Pull Request, `make presubmit` can catch most of the issues your change can run into.
|
||||
```bash
|
||||
$kubectl --namespace open-match get pods,svc
|
||||
|
||||
Our [continuous integration](https://console.cloud.google.com/cloud-build/builds?project=open-match-build)
|
||||
runs against all PRs. In order to see your build results you'll need to
|
||||
become a member of
|
||||
[open-match-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/open-match-discuss).
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/om-clientloadgen-b6cf884cd-nslrl 1/1 Running 0 19s
|
||||
pod/om-evaluator-7795968f9-729qs 1/1 Running 0 19s
|
||||
pod/om-function-697db9cd6-xh89j 1/1 Running 0 19s
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/om-function ClusterIP 10.0.4.81 <none> 50502/TCP,51502/TCP 20s
|
||||
```
|
||||
|
||||
At this point, the Evaluator and Match Function are both running and a client load generator is continuously adding players to the state storage. To see match generation in action, run the following command:
|
||||
|
||||
```bash
|
||||
make run-backendclient
|
||||
```
|
||||
|
||||
Some other handy commands:
|
||||
|
||||
```bash
|
||||
# Cleanup the installation
|
||||
make delete-chart delete-example-chart
|
||||
|
||||
# To install a pre-built image without building again:
|
||||
make REGISTRY=$REGISTRY TAG=$TAG install-chart install-example-chart
|
||||
make REGISTRY=$REGISTRY TAG=$TAG install-example-chart
|
||||
make REGISTRY=$REGISTRY TAG=$TAG run-backendclient
|
||||
```
|
||||
|
||||
**Note**: The programs provided below are just bare-bones manual testing programs with no automation and no claim of code coverage. This sparseness of this part of the documentation is because we expect to discard all of these tools and write a fully automated end-to-end test suite and a collection of load testing tools, with extensive stats output and tracing capabilities before 1.0 release. Tracing has to be integrated first, which will be in an upcoming release.
|
||||
|
||||
In the end: *caveat emptor*. These tools all work and are quite small, and as such are fairly easy for developers to understand by looking at the code and logging output. They are provided as-is just as a reference point of how to begin experimenting with Open Match integrations.
|
||||
|
26
docs/gcloud.md
Normal file
26
docs/gcloud.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Create a GKE Cluster
|
||||
|
||||
Below are the steps to create a GKE cluster in Google Cloud Platform.
|
||||
|
||||
* Create a GCP project via [Google Cloud Console](https://console.cloud.google.com/).
|
||||
* Billing must be enabled. If you're a new customer you can get some [free credits](https://cloud.google.com/free/).
|
||||
* When you create a project you'll need to set a Project ID, if you forget it you can see it here, https://console.cloud.google.com/iam-admin/settings/project.
|
||||
* Install [Google Cloud SDK](https://cloud.google.com/sdk/) which is the command line tool to work against your project.
|
||||
|
||||
Here are the next steps using the gcloud tool.
|
||||
|
||||
```bash
|
||||
# Login to your Google Account for GCP
|
||||
gcloud auth login
|
||||
gcloud config set project $YOUR_GCP_PROJECT_ID
|
||||
|
||||
# Enable necessary GCP services
|
||||
gcloud services enable containerregistry.googleapis.com
|
||||
gcloud services enable container.googleapis.com
|
||||
|
||||
# Test that everything is good, this command should work.
|
||||
gcloud compute zones list
|
||||
|
||||
# Create a GKE Cluster in this project
|
||||
gcloud container clusters create --machine-type n1-standard-4 open-match-dev-cluster --zone us-west1-a --tags open-match
|
||||
```
|
@ -2,49 +2,44 @@
|
||||
|
||||
This is the {version} release of Open Match.
|
||||
|
||||
Check the [official website](https://open-match.dev) for details on features, installation and usage.
|
||||
Check the [README](https://github.com/GoogleCloudPlatform/open-match/tree/release-{version}) for details on features, installation and usage.
|
||||
|
||||
Release Notes
|
||||
-------------
|
||||
|
||||
**Feature Highlights**
|
||||
{ highlight here the most notable changes and themes at a high level}
|
||||
{ insert enhancements from the changelog and/or security and breaking changes }
|
||||
|
||||
**Breaking Changes**
|
||||
{ detail any behaviors or API surfaces which worked in a previous version which will no longer work correctly }
|
||||
|
||||
> Future releases towards 1.0.0 may still have breaking changes.
|
||||
|
||||
**Security Fixes**
|
||||
{ list any changes which fix vulnerabilities in open match }
|
||||
* API Changed #PR
|
||||
|
||||
**Enhancements**
|
||||
{ go into details on improvements and changes }
|
||||
* New Harness #PR
|
||||
|
||||
Usage Requirements
|
||||
-------------
|
||||
* Tested against Kubernetes Version { a list of k8s versions}
|
||||
* Golang Version = v{ required golang version }
|
||||
**Security Fixes**
|
||||
* Reduced privileges required for MMF. #PR
|
||||
|
||||
See [CHANGELOG](https://github.com/GoogleCloudPlatform/open-match/blob/release-{version}/CHANGELOG.md) for more details on changes.
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
```bash
|
||||
# Servers
|
||||
docker pull gcr.io/open-match-public-images/openmatch-backend:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-frontend:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-query:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-synchronizer:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-backendapi:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-frontendapi:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmforc:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmlogicapi:{version}
|
||||
|
||||
# Evaluators
|
||||
docker pull gcr.io/open-match-public-images/openmatch-evaluator-go-simple:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-evaluator-serving:{version}
|
||||
|
||||
# Sample Match Making Functions
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmf-go-soloduel:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmf-go-pool:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmf-go-grpc-serving-simple:{version}
|
||||
|
||||
# Test Clients
|
||||
docker pull gcr.io/open-match-public-images/openmatch-demo-first-match:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-backendclient:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-clientloadgen:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-frontendclient:{version}
|
||||
```
|
||||
|
||||
_This software is currently alpha, and subject to change. Not to be used in production systems._
|
||||
@ -52,10 +47,15 @@ _This software is currently alpha, and subject to change. Not to be used in prod
|
||||
Installation
|
||||
------------
|
||||
|
||||
* Follow [Open Match Installation Guide](https://open-match.dev/site/docs/installation/) to setup Open Match in your cluster.
|
||||
To deploy Open Match in your Kubernetes cluster run the following commands:
|
||||
|
||||
API Definitions
|
||||
------------
|
||||
|
||||
- gRPC API Definitions are available in [API references](https://open-match.dev/site/docs/reference/api/) - _Preferred_
|
||||
- HTTP API Definitions are available in [SwaggerUI](https://open-match.dev/site/swaggerui/index.html)
|
||||
```bash
|
||||
# Grant yourself cluster-admin permissions so that you can deploy service accounts.
|
||||
kubectl create clusterrolebinding myname-cluster-admin-binding --clusterrole=cluster-admin --user=$(YOUR_KUBERNETES_USER_NAME)
|
||||
# Place all Open Match components in their own namespace.
|
||||
kubectl create namespace open-match
|
||||
# Install Open Match and monitoring services.
|
||||
kubectl apply -f https://github.com/GoogleCloudPlatform/open-match/releases/download/v{version}/install.yaml --namespace open-match
|
||||
# Install the example MMF and Evaluator.
|
||||
kubectl apply -f https://github.com/GoogleCloudPlatform/open-match/releases/download/v{version}/install-example.yaml --namespace open-match
|
||||
```
|
||||
|
@ -12,13 +12,24 @@ SOURCE_VERSION=$1
|
||||
DEST_VERSION=$2
|
||||
SOURCE_PROJECT_ID=open-match-build
|
||||
DEST_PROJECT_ID=open-match-public-images
|
||||
IMAGE_NAMES=$(make list-images)
|
||||
IMAGE_NAMES="openmatch-backendapi openmatch-frontendapi openmatch-mmforc openmatch-mmlogicapi openmatch-evaluator-serving openmatch-mmf-go-grpc-serving-simple openmatch-backendclient openmatch-clientloadgen openmatch-frontendclient"
|
||||
|
||||
for name in $IMAGE_NAMES
|
||||
do
|
||||
source_image=gcr.io/$SOURCE_PROJECT_ID/openmatch-$name:$SOURCE_VERSION
|
||||
dest_image=gcr.io/$DEST_PROJECT_ID/openmatch-$name:$DEST_VERSION
|
||||
source_image=gcr.io/$SOURCE_PROJECT_ID/$name:$SOURCE_VERSION
|
||||
dest_image=gcr.io/$DEST_PROJECT_ID/$name:$DEST_VERSION
|
||||
docker pull $source_image
|
||||
docker tag $source_image $dest_image
|
||||
docker push $dest_image
|
||||
done
|
||||
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
|
||||
echo "Add these lines to your release notes:"
|
||||
for name in $IMAGE_NAMES
|
||||
do
|
||||
echo "docker pull gcr.io/$DEST_PROJECT_ID/$name:$DEST_VERSION"
|
||||
done
|
||||
|
82
docs/governance/templates/release_issue.md
Normal file
82
docs/governance/templates/release_issue.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Release {version}
|
||||
|
||||
<!--
|
||||
This is the release issue template. Make a copy of the markdown in this page
|
||||
and copy it into a release issue. Fill in relevent values, found inside {}
|
||||
{version} should be replaced with the version ie: 0.5.0.
|
||||
|
||||
There are 3 types of releases:
|
||||
* Release Candidates - 1.0.0-rc1
|
||||
* Full Releases - 1.2.0
|
||||
* Hot Fixes - 1.0.1
|
||||
|
||||
# Release Candidate and Full Release Process
|
||||
|
||||
1. Create a Release Issue from the [release issue template](./release_issue.md).
|
||||
1. Label the issue `kind/release`, and attach it to the milestone that it matches.
|
||||
1. Complete all items in the release issue checklist.
|
||||
1. Close the release issue.
|
||||
|
||||
# Hot Fix Process
|
||||
|
||||
1. Hotfixes will occur as needed, to be determined by those will commit access on the repository.
|
||||
1. Create a Release Issue from the [release issue template](./release_issue.md).
|
||||
1. Label the issue `kind/release`, and attach it to the next upcoming milestone.
|
||||
1. Complete all items in the release issue checklist.
|
||||
1. Close the release issue.
|
||||
|
||||
!-->
|
||||
Complete Milestone
|
||||
------------------
|
||||
- [ ] Create the next version milestone, use [semantic versioning](https://semver.org/) when naming it to be consistent with the [Go community](https://blog.golang.org/versioning-proposal).
|
||||
- [ ] Visit the [milestone](https://github.com/GoogleCloudPlatform/open-match/milestone).
|
||||
- [ ] Open a document for a draft [release notes](release.md).
|
||||
- [ ] Add the milestone tag to all PRs and issues that were merged since the last milestone. Look at the [releases page](https://github.com/GoogleCloudPlatform/open-match/releases) and look for the "X commits to master since this release" for the diff. The link resolves to, https://github.com/GoogleCloudPlatform/open-match/compare/v{version}...master.
|
||||
- [ ] Review all [milestone-less closed issues](https://github.com/GoogleCloudPlatform/open-match/issues?q=is%3Aissue+is%3Aclosed+no%3Amilestone) and assign the appropriate milestone.
|
||||
- [ ] Review all [issues in milestone](https://github.com/GoogleCloudPlatform/open-match/milestones) for proper [labels](https://github.com/GoogleCloudPlatform/open-match/labels) (ex: area/build).
|
||||
- [ ] Review all [milestone-less closed PRs](https://github.com/GoogleCloudPlatform/open-match/pulls?q=is%3Apr+is%3Aclosed+no%3Amilestone) and assign the appropriate milestone.
|
||||
- [ ] Review all [PRs in milestone](https://github.com/GoogleCloudPlatform/open-match/milestones) for proper [labels](https://github.com/GoogleCloudPlatform/open-match/labels) (ex: area/build).
|
||||
- [ ] View all open entries in milestone and move them to a future milestone if they aren't getting closed in time. https://github.com/GoogleCloudPlatform/open-match/milestones/v{version}
|
||||
- [ ] Review all closed PRs against the milestone. Put the user visible changes into the release notes using the suggested format. https://github.com/GoogleCloudPlatform/open-match/pulls?q=is%3Apr+is%3Aclosed+milestone%3Av{version}
|
||||
- [ ] Review all closed issues against the milestone. Put the user visible changes into the release notes using the suggested format. https://github.com/GoogleCloudPlatform/open-match/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Av{version}
|
||||
- [ ] Verify the [milestone](https://github.com/GoogleCloudPlatform/open-match/milestones) is effectively 100% at this point with the exception of the release issue itself.
|
||||
|
||||
TODO: Add details for appropriate tagging for issues.
|
||||
|
||||
Build Artifacts
|
||||
---------------
|
||||
- [ ] Create a PR to bump the version.
|
||||
- [ ] Open the [`Makefile`](makefile-version) and change BASE_VERSION value. Release candidates use the -rc# suffix.
|
||||
- [ ] Open the [`install/helm/open-match/Chart.yaml`](om-chart-yaml-version) and [`install/helm/open-match-example/Chart.yaml`](om-example-chart-yaml-version) and change the `appVersion` and `version` entries.
|
||||
- [ ] Open the [`install/helm/open-match/values.yaml`](om-values-yaml-version) and [`install/helm/open-match-example/values.yaml`](om-example-values-yaml-version) and change the `tag` entries.
|
||||
- [ ] Open the [`site/config.toml`] and change the `release_branch` and `release_version` entries.
|
||||
- [ ] Open the [`README.md`](readme-deploy) update the version references.
|
||||
- [ ] Run `make clean release`
|
||||
- [ ] There might be additional references to the old version but be careful not to change it for places that have it for historical purposes.
|
||||
- [ ] Submit the pull request.
|
||||
- [ ] Take note of the git hash in master, `git checkout master && git pull master && git rev-parse HEAD`
|
||||
- [ ] Go to [Cloud Build](https://pantheon.corp.google.com/cloud-build/triggers?project=open-match-build), under Post Submit click "Run Trigger".
|
||||
- [ ] Go to the History section and find the "Post Submit" build that's running. Wait for it to go Green. If it's red fix error repeat this section. Take note of version tag for next step.
|
||||
- [ ] Run `./docs/governance/templates/release.sh {source version tag} {version}` to copy the images to open-match-public-images.
|
||||
- [ ] Create a *draft* release with the [release template][release-template]
|
||||
- [ ] Make a `tag` with the release version. The tag must be v{version}. Example: v0.5.0. Append -rc# for release candidates. Example: v0.5.0-rc1.
|
||||
- [ ] Copy the files from `build/release/` generated from `make release` from earlier as release artifacts.
|
||||
- [ ] Run `make delete-gke-cluster create-gke-cluster push-helm sleep-10 install-chart install-example-chart` and verify that the pods are all healthy.
|
||||
- [ ] Run `make delete-gke-cluster create-gke-cluster` and run through the instructions under the [README](readme-deploy), verify the pods are healthy. You'll need to adjust the path to the `install/yaml/install.yaml` and `install/yaml/install-example.yaml` in your local clone since you haven't published them yet.
|
||||
- [ ] Publish the [Release](om-release) in Github.
|
||||
|
||||
Announce
|
||||
--------
|
||||
- [ ] Send an email to the [mailing list](mailing-list-post) with the release details (copy-paste the release blog post)
|
||||
- [ ] Send a chat on the [Slack channel](om-slack). "Open Match {version} has been released! Check it out at {release url}."
|
||||
|
||||
[om-slack]: https://open-match.slack.com/
|
||||
[mailing-list-post]: https://groups.google.com/forum/#!newtopic/open-match-discuss
|
||||
[release-template]: https://github.com/GoogleCloudPlatform/open-match/blob/master/docs/governance/templates/release.md
|
||||
[makefile-version]: https://github.com/GoogleCloudPlatform/open-match/blob/master/Makefile#L53
|
||||
[om-example-chart-yaml-version]: https://github.com/GoogleCloudPlatform/open-match/blob/master/install/helm/open-match/Chart.yaml#L16
|
||||
[om-example-values-yaml-version]: https://github.com/GoogleCloudPlatform/open-match/blob/master/install/helm/open-match/values.yaml#L16
|
||||
[om-example-chart-yaml-version]: https://github.com/GoogleCloudPlatform/open-match/blob/master/install/helm/open-match-example/Chart.yaml#L16
|
||||
[om-example-values-yaml-version]: https://github.com/GoogleCloudPlatform/open-match/blob/master/install/helm/open-match-example/values.yaml#L16
|
||||
[om-release]: https://github.com/GoogleCloudPlatform/open-match/releases/new
|
||||
[readme-deploy]: https://github.com/GoogleCloudPlatform/open-match/blob/master/README.md#deploy-to-kubernetes
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "Open Match API References"
|
||||
linkTitle: "Open Match API References"
|
||||
weight: 2
|
||||
description:
|
||||
This document provides API references for Open Match services.
|
||||
---
|
17
docs/integrations.md
Normal file
17
docs/integrations.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Open Source Software integrations
|
||||
|
||||
### Structured Logging - Logrus
|
||||
|
||||
Logging for Open Match uses the [Golang logrus module](https://github.com/sirupsen/logrus) to provide structured logs. Logs are output to `stdout` in each component, as expected by Docker and Kubernetes. Level and format are configurable via config/matchmaker_config.json. If you have a specific log aggregator as your final destination, we recommend you have a look at the logrus documentation as there is probably a log formatter that plays nicely with your stack.
|
||||
|
||||
### Instrumentation - OpenCensus
|
||||
|
||||
Open Match uses [OpenCensus](https://opencensus.io/) for metrics instrumentation. The [gRPC](https://grpc.io/) integrations are built-in, and Golang redigo module integrations are incoming, but [haven't been merged into the official repo](https://github.com/opencensus-integrations/redigo/pull/1). All of the core components expose HTTP `/metrics` endpoints on the port defined in `config/matchmaker_config.json` (default: 9555) for Prometheus to scrape. If you would like to export to a different metrics aggregation platform, we suggest you have a look at the OpenCensus documentation — there may be one written for you already, and switching to it may be as simple as changing a few lines of code.
|
||||
|
||||
**Note:** A standard for instrumentation of MMFs is planned.
|
||||
|
||||
### State Storage - Redis
|
||||
|
||||
By default, Open Match expects you to run Redis *somewhere*. Connection information can be put in the config file (`matchmaker_config.json`) for any Redis instance reachable from the [Kubernetes namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/). By default, Open Match sensibly runs in the Kubernetes `default` namespace. In most instances, we expect users will run a copy of Redis in a pod in Kubernetes, with a service pointing to it.
|
||||
|
||||
* HA configurations for Redis aren't implemented by the provided Kubernetes resource definition files, but Open Match expects the Redis service to be named `redis`, which provides an easier path to multi-instance deployments.
|
12
docs/references.md
Normal file
12
docs/references.md
Normal file
@ -0,0 +1,12 @@
|
||||
## Additional References
|
||||
|
||||
### Docker
|
||||
- [Docker's official "Getting Started" guide](https://docs.docker.com/get-started/)
|
||||
- [Katacoda's free, interactive Docker course](https://www.katacoda.com/courses/docker)
|
||||
|
||||
### Kubernetes
|
||||
- [You should totally read this comic, and interactive tutorial](https://cloud.google.com/kubernetes-engine/kubernetes-comic/)
|
||||
- [Katacoda's free, interactive Kubernetes course](https://www.katacoda.com/courses/kubernetes)
|
||||
|
||||
### Prometheus
|
||||
- [Prometheus Operator spec](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md)
|
68
docs/roadmap.md
Normal file
68
docs/roadmap.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Open Match Roadmap
|
||||
|
||||
Open Match is currently at release 0.4.0. Open Match 0.5.0 currently has a Release Candidate and we are targeting to cut the release on 04/25/2019.
|
||||
|
||||
Releases can be found on the [releases page](https://github.com/GoogleCloudPlatform/open-match/releases).
|
||||
|
||||
Below sections detail the themes and the roadmap for the future releases. The tasks listed for the 0.6.0 release have been finalized and are well understood. As for the 0.7.0 and beyond, the tasks currently identified are listed. These are subject to change as we make our way through 0.6.0 release and get more feedback from the community.
|
||||
|
||||
## 0.5.0 - Usability
|
||||
|
||||
The primary focus of the 0.5 release is usability. The goal for this release is to make Open Match easy to build and deploy and have solid supporting documentation. Users should be able to try Open Match 0.5.0 functionality and experiment with its features, MMFs etc. Here are some planned features for this release:
|
||||
|
||||
- [X] Add support to invoke MMFs as a gRPC function call.
|
||||
- [X] Provide a gRPC serving harness and an example MMF built using this harness. (golang based).
|
||||
- [X] Provide a evaluation harness and a sample evaluator using this harness (golang based)
|
||||
- [X] Deprecate the k8s based job scheduling mechanism for MMFs, Evaluator in favor of hosted MMFs, Evaluator.
|
||||
- [X] Switch all core Open Match services to use gRPC style request / response protos.
|
||||
- [X] Documentation: Add basic user, developer documentation and set up the Open Match website.
|
||||
- [X] Create and document a formal release process.
|
||||
- [X] Improve developer experience (simplify compiling, deploying and validating)
|
||||
|
||||
## 0.6.0 - API changes, Maturity
|
||||
|
||||
In 0.6.0 release, we are revisiting the Data Model and the API surface exposed by Open Match. The goal of this release is to front-load a major API refactoring that will facilitate achieving scale and other productionizing goals in forthcoming releases. Although breaking chagnes can happen any time till 1.0, the goal is to implement any major breaking changes in 0.6.0 so that future chagnes if any are relatively minor. Customer should be able to start building their Match Makers using the 0.6.0 API surface.
|
||||
|
||||
Here are the tasks planned for 0.6.0 release:
|
||||
|
||||
- [ ] Implement the new Data model and the API changes for the Frontend, Backend and MMLogic API [Change Proposal](https://github.com/GoogleCloudPlatform/open-match/issues/279)
|
||||
- [ ] Accept multiple proposals per MMF execution.
|
||||
- [ ] Remove persistance of matches and proposals from Open Match state storage.
|
||||
- [ ] Implement synchronized evaluation to eliminate use of state storage during evaluation.
|
||||
- [ ] Introduce test framework for unit testing, Component testing and E2E testing.
|
||||
- [ ] Add unit tests, component tests and integration tests for Open Match core components and examples.
|
||||
- [ ] Update harness, evaluator, mmf samples etc., to reflect the API changes.
|
||||
- [ ] Update documentation, website to reflect 0.6.0 API changes.
|
||||
|
||||
## 0.7.0 - Scale, Operationalizing
|
||||
|
||||
Features for 0.7.0 are targeted to enable Open Match to be productionizable. Note that as we identify more feature work past 0.6.0, these tasks may get pushed to future releases. However, these are core tasks that need to be addressed before Open Match reaches 1.0.
|
||||
|
||||
- [ ] Introduce Test framework for load, performance testing
|
||||
- [ ] Automated Load / performance / scale tests
|
||||
- [ ] Test results Dashboard
|
||||
- [ ] Add support for Instrumentation, Monitoring, Dashboards
|
||||
- [ ] Add support or Metrics collection, Analytics, Dashboards
|
||||
- [ ] Identify Autoscaling patterns for each component and configure them.
|
||||
|
||||
## Other Features
|
||||
|
||||
Below are additional features that are not tied to a specific release but will be added incrementally across releases:
|
||||
|
||||
- [ ] Harness support for Python, PHP, C#, C++
|
||||
- [ ] User Guide for Open Match, Tutorials
|
||||
- [ ] Developer Guide for Open Match
|
||||
- [ ] APIs & Reference
|
||||
- [ ] Concept Documentation
|
||||
- [ ] Website Improvements
|
||||
|
||||
## 1.1.0
|
||||
|
||||
Below are the features that have been identified but are not considered critical for Open Match (as a match making framework) to itself reach 1.0. Any other features that are related to Open Match ecosystem but not a part of the framework itself can be listed here. These features may not necessarily wait for Open Match 1.0 and can be implemented before that - but any of the currently identified 1.0 tasks are higher in priority than these to make Open Match production ready.
|
||||
|
||||
- [ ] Canonical usable examples out of box.
|
||||
- [ ] KNative support to run MMFs
|
||||
- [ ] OSS Director to integrate with Agones, other DGS backends
|
||||
|
||||
### Special Thanks
|
||||
- Thanks to https://jbt.github.io/markdown-editor/ for help in marking this document down.
|
24
examples/backendclient/Dockerfile
Normal file
24
examples/backendclient/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/backendclient/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/examples/backendclient/backendclient .
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/examples/backendclient/profiles profiles
|
||||
|
||||
ENTRYPOINT ["/backendclient"]
|
234
examples/backendclient/main.go
Normal file
234
examples/backendclient/main.go
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
Stubbed backend api client. This should be run within a k8s cluster, and
|
||||
assumes that the backend api is up and can be accessed through a k8s service
|
||||
named om-backendapi
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
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
|
||||
|
||||
https://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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/gobs/pretty"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
filename = flag.String("file", "profiles/testprofile.json", "JSON file from which to read match properties")
|
||||
beCall = flag.String("call", "ListMatches", "Open Match backend match request gRPC call to test")
|
||||
server = flag.String("backend", "om-backendapi:50505", "Hostname and IP of the Open Match backend")
|
||||
assignment = flag.String("assignment", "example.server.dgs:12345", "Assignment to send to matched players")
|
||||
delAssignments = flag.Bool("rm", false, "Delete assignments. Leave off to be able to manually validate assignments in state storage")
|
||||
verbose = flag.Bool("verbose", false, "Print out as much as possible")
|
||||
runForever = flag.Bool("loop", true, "Make the desired call in a loop till process terminates")
|
||||
runInterval = flag.Int("interval", 5, "seconds to wait between consequitive calls")
|
||||
)
|
||||
|
||||
func bytesToString(data []byte) string {
|
||||
return string(data[:])
|
||||
}
|
||||
|
||||
func ppJSON(s string) {
|
||||
if *verbose {
|
||||
buf := new(bytes.Buffer)
|
||||
json.Indent(buf, []byte(s), "", " ")
|
||||
log.Println(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.Print("Parsing flags:")
|
||||
log.Printf(" [flags] Reading properties from file at %v", *filename)
|
||||
log.Printf(" [flags] Using OM Backend address %v", *server)
|
||||
log.Printf(" [flags] Using OM Backend %v call", *beCall)
|
||||
log.Printf(" [flags] Assigning players to %v", *assignment)
|
||||
log.Printf(" [flags] Deleting assignments? %v", *delAssignments)
|
||||
log.Printf(" [flags] Run forever? %v", *runForever)
|
||||
log.Printf(" [flags] Interval between consequitive runs - %v", *runInterval)
|
||||
if !(*beCall == "CreateMatch" || *beCall == "ListMatches") {
|
||||
log.Printf(" [flags] Unknown OM Backend call %v! Exiting...", *beCall)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the profile
|
||||
jsonFile, err := os.Open(*filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file %v", *filename)
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
// parse json data and remove extra whitespace before sending to the backend.
|
||||
jsonData, _ := ioutil.ReadAll(jsonFile) // this reads as a byte array
|
||||
buffer := new(bytes.Buffer) // convert byte array to buffer to send to json.Compact()
|
||||
if err := json.Compact(buffer, jsonData); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
jsonProfile := buffer.String()
|
||||
pbProfile := &pb.MatchObject{}
|
||||
pbProfile.Properties = jsonProfile
|
||||
|
||||
conn, err := grpc.Dial(*server, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect: %s", err.Error())
|
||||
}
|
||||
|
||||
client := pb.NewBackendClient(conn)
|
||||
log.Println("Backend client connected to", *server)
|
||||
|
||||
var profileName string
|
||||
if gjson.Get(jsonProfile, "name").Exists() {
|
||||
profileName = gjson.Get(jsonProfile, "name").String()
|
||||
} else {
|
||||
profileName = "testprofilename"
|
||||
log.Println("JSON Profile does not contain a name; using ", profileName)
|
||||
}
|
||||
|
||||
pbProfile.Id = profileName
|
||||
pbProfile.Properties = jsonProfile
|
||||
|
||||
mmfcfg := &pb.MmfConfig{Name: "profileName"}
|
||||
mmfcfg.Type = pb.MmfConfig_GRPC
|
||||
mmfcfg.Host = gjson.Get(jsonProfile, "hostname").String()
|
||||
mmfcfg.Port = int32(gjson.Get(jsonProfile, "port").Int())
|
||||
|
||||
req := &pb.CreateMatchRequest{
|
||||
Match: pbProfile,
|
||||
Mmfcfg: mmfcfg,
|
||||
}
|
||||
|
||||
log.Println("Backend Request:")
|
||||
ppJSON(jsonProfile)
|
||||
pretty.PrettyPrint(mmfcfg)
|
||||
|
||||
log.Printf("Establishing HTTPv2 stream...")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
matchChan := make(chan *pb.MatchObject)
|
||||
doneChan := make(chan bool)
|
||||
go func() {
|
||||
// Watch for results and print as they come in.
|
||||
log.Println("Watching for match results...")
|
||||
for {
|
||||
select {
|
||||
case match := <-matchChan:
|
||||
if match.Error == "insufficient players" {
|
||||
log.Println("Waiting for a larger player pool...")
|
||||
}
|
||||
|
||||
// Validate JSON before trying to parse it
|
||||
if !gjson.Valid(string(match.Properties)) {
|
||||
log.Println(errors.New("invalid json"))
|
||||
}
|
||||
log.Println("Received match:")
|
||||
pretty.PrettyPrint(match)
|
||||
|
||||
// Assign players in this match to our server
|
||||
log.Println("Assigning players to DGS at", *assignment)
|
||||
|
||||
assign := &pb.Assignments{Rosters: match.Rosters, Assignment: *assignment}
|
||||
log.Printf("Waiting for matches...")
|
||||
_, err = client.CreateAssignments(context.Background(), &pb.CreateAssignmentsRequest{
|
||||
Assignment: assign,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
log.Println("Success!")
|
||||
|
||||
if *delAssignments {
|
||||
log.Println("deleting assignments")
|
||||
for _, a := range assign.Rosters {
|
||||
_, err = client.DeleteAssignments(context.Background(), &pb.DeleteAssignmentsRequest{Roster: a})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
log.Println("Success Deleting Assignments!")
|
||||
}
|
||||
} else {
|
||||
log.Println("Not deleting assignments [demo mode].")
|
||||
}
|
||||
}
|
||||
if *beCall == "CreateMatch" {
|
||||
// Got a result; done here.
|
||||
log.Println("Got single result from CreateMatch, exiting...")
|
||||
doneChan <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Make the requested backend call: CreateMatch calls once, ListMatches continually calls.
|
||||
log.Printf("Attempting %v() call", *beCall)
|
||||
for {
|
||||
switch *beCall {
|
||||
case "CreateMatch":
|
||||
resp, err := client.CreateMatch(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("Failed CreateMatch, %v", err)
|
||||
break
|
||||
}
|
||||
log.Printf("CreateMatch returned; processing match")
|
||||
|
||||
matchChan <- resp.Match
|
||||
<-doneChan
|
||||
case "ListMatches":
|
||||
stream, err := client.ListMatches(ctx, &pb.ListMatchesRequest{
|
||||
Mmfcfg: req.Mmfcfg,
|
||||
Match: req.Match,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed ListMatches, %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
for {
|
||||
log.Printf("Waiting for matches...")
|
||||
resp, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error reading stream for ListMatches, %v", err)
|
||||
break
|
||||
}
|
||||
matchChan <- resp.Match
|
||||
}
|
||||
}
|
||||
|
||||
if !*runForever {
|
||||
break
|
||||
}
|
||||
|
||||
// Wait for the retry interval before calling again.
|
||||
time.Sleep(time.Duration(*runInterval) * time.Second)
|
||||
}
|
||||
}
|
53
examples/backendclient/profiles/testprofile.json
Normal file
53
examples/backendclient/profiles/testprofile.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name":"testprofilev1",
|
||||
"id":"testprofile",
|
||||
"hostname": "om-function",
|
||||
"port": 50502,
|
||||
"properties":{
|
||||
"pools": [
|
||||
{
|
||||
"name": "defaultPool",
|
||||
"filters": [
|
||||
{ "name": "europeWest1ElapsedUnder150", "attribute": "region.europe-west1", "maxv": "150" },
|
||||
{ "name": "silverRanking", "attribute": "mmr.rating", "maxv": "1250", "minv": "950" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "supportPool",
|
||||
"filters": [
|
||||
{ "name": "europeWest1ElapsedUnder150", "attribute": "region.europe-west1", "maxv": "150" },
|
||||
{ "name": "silverRanking", "attribute": "mmr.rating", "maxv": "1250", "minv": "950" },
|
||||
{ "name": "supportRole", "attribute": "role.support", "maxv": "2147483647" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"rosters": [
|
||||
{
|
||||
"name": "red",
|
||||
"players": [
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "blu",
|
||||
"players": [
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 bytesub provides the ability for many clients to subscribe to the
|
||||
// latest value of a byte array.
|
||||
package bytesub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var partialWriteError = errors.New("ByteSub subscriber didn't consume the whole message.")
|
||||
|
||||
type ByteSub struct {
|
||||
nextReady chan struct{}
|
||||
b []byte
|
||||
r sync.RWMutex
|
||||
}
|
||||
|
||||
func New() *ByteSub {
|
||||
return &ByteSub{
|
||||
nextReady: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// AnnounceLatest writes b to all of the subscribers, with caviets listed in Subscribe.
|
||||
func (s *ByteSub) AnnounceLatest(b []byte) {
|
||||
s.r.Lock()
|
||||
defer s.r.Unlock()
|
||||
close(s.nextReady)
|
||||
s.nextReady = make(chan struct{})
|
||||
s.b = b
|
||||
}
|
||||
|
||||
func (s *ByteSub) get() (b []byte, nextReady chan struct{}) {
|
||||
s.r.RLock()
|
||||
defer s.r.RUnlock()
|
||||
return s.b, s.nextReady
|
||||
}
|
||||
|
||||
// Subscribe writes the latest value in a single call to w.Write. It does not
|
||||
// guarantee that all values published to AnnounceLatest will be written.
|
||||
// However once things catch up, it will write the latest value. If no values
|
||||
// have been announced, waits for a value before writing.
|
||||
func (s *ByteSub) Subscribe(ctx context.Context, w io.Writer) error {
|
||||
nextReady := make(chan struct{})
|
||||
close(nextReady)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-nextReady:
|
||||
var b []byte
|
||||
b, nextReady = s.get()
|
||||
if b != nil {
|
||||
l, err := w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if l != len(b) {
|
||||
return partialWriteError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 bytesub provides the ability for many clients to subscribe to the
|
||||
// latest value of a byte array.
|
||||
package bytesub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFastAndSlow ensures that if a slow subscriber is blocked, faster subscribers
|
||||
// nor publishers aren't blocked. It also ensures that values published while slow
|
||||
// wasn't listening are skipped.
|
||||
func TestFastAndSlow(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
fast := make(chan string)
|
||||
slow := make(chan string)
|
||||
|
||||
s := New()
|
||||
|
||||
go func() {
|
||||
s.Subscribe(ctx, chanWriter{fast})
|
||||
close(fast)
|
||||
}()
|
||||
go func() {
|
||||
s.Subscribe(ctx, chanWriter{slow})
|
||||
close(slow)
|
||||
}()
|
||||
|
||||
for _, i := range []string{"0", "1", "2", "3"} {
|
||||
s.AnnounceLatest([]byte(i))
|
||||
if v := <-fast; v != i {
|
||||
t.Errorf("Expected \"%s\", got \"%s\"", i, v)
|
||||
}
|
||||
}
|
||||
|
||||
for count := 0; true; count++ {
|
||||
if v := <-slow; v == "3" {
|
||||
if count > 1 {
|
||||
t.Error("Expected to recieve at most 1 other value on slow before recieving the latest value.")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
_, ok := <-fast
|
||||
if ok {
|
||||
t.Error("Expected subscribe to return and fast to be closed")
|
||||
}
|
||||
_, ok = <-slow
|
||||
if ok {
|
||||
t.Error("Expected subscribe to return and slow to be closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadWriter(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
s := New()
|
||||
s.AnnounceLatest([]byte{0, 1, 2})
|
||||
err := s.Subscribe(ctx, writerFunc(func(b []byte) (int, error) {
|
||||
return 1, nil
|
||||
}))
|
||||
|
||||
if err != partialWriteError {
|
||||
t.Errorf("Expected partialWriteError, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorReturned(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
expected := errors.New("Hello there.")
|
||||
|
||||
s := New()
|
||||
s.AnnounceLatest([]byte{0, 1, 2})
|
||||
err := s.Subscribe(ctx, writerFunc(func(b []byte) (int, error) {
|
||||
return 0, expected
|
||||
}))
|
||||
|
||||
if err != expected {
|
||||
t.Errorf("Expected returned error, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextError(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
s := New()
|
||||
s.AnnounceLatest([]byte{0, 1, 2})
|
||||
err := s.Subscribe(ctx, writerFunc(func(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}))
|
||||
|
||||
if err != context.Canceled {
|
||||
t.Errorf("Expected context canceled error, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type chanWriter struct {
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (cw chanWriter) Write(b []byte) (int, error) {
|
||||
cw.c <- string(b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
type writerFunc func(b []byte) (int, error)
|
||||
|
||||
func (w writerFunc) Write(b []byte) (int, error) {
|
||||
return w(b)
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
func Run(ds *components.DemoShared) {
|
||||
u := updater.NewNested(ds.Ctx, ds.Update)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
name := fmt.Sprintf("fakeplayer_%d", i)
|
||||
go func() {
|
||||
for !isContextDone(ds.Ctx) {
|
||||
runScenario(ds.Ctx, name, u.ForField(name))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func isContextDone(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type status struct {
|
||||
Status string
|
||||
Assignment *pb.Assignment
|
||||
}
|
||||
|
||||
func runScenario(ctx context.Context, name string, update updater.SetFunc) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
|
||||
update(status{Status: fmt.Sprintf("Encountered error: %s", err.Error())})
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
}()
|
||||
|
||||
s := status{}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Main Menu"
|
||||
update(s)
|
||||
|
||||
time.Sleep(time.Duration(rand.Int63()) % (time.Second * 15))
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Connecting to Open Match frontend"
|
||||
update(s)
|
||||
|
||||
// See https://open-match.dev/site/docs/guides/api/
|
||||
conn, err := grpc.Dial("open-match-frontend.open-match.svc.cluster.local:50504", grpc.WithInsecure())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
fe := pb.NewFrontendServiceClient(conn)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Creating Open Match Ticket"
|
||||
update(s)
|
||||
|
||||
var ticketId string
|
||||
{
|
||||
req := &pb.CreateTicketRequest{
|
||||
Ticket: &pb.Ticket{},
|
||||
}
|
||||
|
||||
resp, err := fe.CreateTicket(ctx, req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ticketId = resp.Id
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = fmt.Sprintf("Waiting match with ticket Id %s", ticketId)
|
||||
update(s)
|
||||
|
||||
var assignment *pb.Assignment
|
||||
{
|
||||
req := &pb.WatchAssignmentsRequest{
|
||||
TicketId: ticketId,
|
||||
}
|
||||
|
||||
stream, err := fe.WatchAssignments(ctx, req)
|
||||
for assignment.GetConnection() == "" {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
// For now we don't expect to get EOF, so that's still an error worthy of panic.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assignment = resp.Assignment
|
||||
}
|
||||
|
||||
err = stream.CloseSend()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Sleeping (pretend this is playing a match...)"
|
||||
s.Assignment = assignment
|
||||
update(s)
|
||||
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 components
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
)
|
||||
|
||||
type DemoShared struct {
|
||||
Ctx context.Context
|
||||
Update updater.SetFunc
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 director
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
func Run(ds *components.DemoShared) {
|
||||
for !isContextDone(ds.Ctx) {
|
||||
run(ds)
|
||||
}
|
||||
}
|
||||
|
||||
func isContextDone(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type status struct {
|
||||
Status string
|
||||
LatestMatches []*pb.Match
|
||||
}
|
||||
|
||||
func run(ds *components.DemoShared) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
|
||||
ds.Update(status{Status: fmt.Sprintf("Encountered error: %s", err.Error())})
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
}()
|
||||
|
||||
s := status{}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Connecting to backend"
|
||||
ds.Update(s)
|
||||
|
||||
// See https://open-match.dev/site/docs/guides/api/
|
||||
conn, err := grpc.Dial("open-match-backend.open-match.svc.cluster.local:50505", grpc.WithInsecure())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
be := pb.NewBackendServiceClient(conn)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Match Match: Sending Request"
|
||||
ds.Update(s)
|
||||
|
||||
var matches []*pb.Match
|
||||
{
|
||||
req := &pb.FetchMatchesRequest{
|
||||
Config: &pb.FunctionConfig{
|
||||
Host: "om-function.open-match-demo.svc.cluster.local",
|
||||
Port: 50502,
|
||||
Type: pb.FunctionConfig_GRPC,
|
||||
},
|
||||
Profile: &pb.MatchProfile{
|
||||
Name: "1v1",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: "Everyone",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
stream, err := be.FetchMatches(ds.Ctx, req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
matches = append(matches, resp.GetMatch())
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Matches Found"
|
||||
s.LatestMatches = matches
|
||||
ds.Update(s)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Assigning Players"
|
||||
ds.Update(s)
|
||||
|
||||
for _, match := range matches {
|
||||
ids := []string{}
|
||||
|
||||
for _, t := range match.Tickets {
|
||||
ids = append(ids, t.Id)
|
||||
}
|
||||
|
||||
req := &pb.AssignTicketsRequest{
|
||||
Assignments: []*pb.AssignmentGroup{
|
||||
{
|
||||
TicketIds: ids,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := be.AssignTickets(ds.Ctx, req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_ = resp
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Sleeping"
|
||||
ds.Update(s)
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 uptime
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
)
|
||||
|
||||
func Run(ds *components.DemoShared) {
|
||||
t := time.NewTicker(time.Second)
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
ds.Update(i)
|
||||
i++
|
||||
case <-ds.Ctx.Done():
|
||||
t.Stop()
|
||||
ds.Update(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 demo contains the core startup code for running a demo.
|
||||
package demo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
"open-match.dev/open-match/examples/demo/bytesub"
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
"open-match.dev/open-match/internal/telemetry"
|
||||
)
|
||||
|
||||
// Run starts the provided components, and hosts a webserver for observing the
|
||||
// output of those components.
|
||||
func Run(comps map[string]func(*components.DemoShared)) {
|
||||
log.Print("Initializing Server")
|
||||
|
||||
fileServe := http.FileServer(http.Dir("/app/static"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fileServe))
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fileServe.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
http.Handle(telemetry.HealthCheckEndpoint, telemetry.NewAlwaysReadyHealthCheck())
|
||||
|
||||
bs := bytesub.New()
|
||||
u := updater.New(context.Background(), func(b []byte) {
|
||||
var out bytes.Buffer
|
||||
err := json.Indent(&out, b, "", " ")
|
||||
if err == nil {
|
||||
bs.AnnounceLatest(out.Bytes())
|
||||
} else {
|
||||
bs.AnnounceLatest(b)
|
||||
}
|
||||
})
|
||||
|
||||
http.Handle("/connect", websocket.Handler(func(ws *websocket.Conn) {
|
||||
bs.Subscribe(ws.Request().Context(), ws)
|
||||
}))
|
||||
|
||||
log.Print("Starting Server")
|
||||
|
||||
for name, f := range comps {
|
||||
go f(&components.DemoShared{
|
||||
Ctx: context.Background(),
|
||||
Update: u.ForField(name),
|
||||
})
|
||||
}
|
||||
|
||||
address := fmt.Sprintf(":%d", 51507)
|
||||
err := http.ListenAndServe(address, nil)
|
||||
log.Printf("HTTP server closed: %s", err.Error())
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
window.onload = function() {
|
||||
let protocol = "ws://";
|
||||
if (window.location.protocol == "https:") {
|
||||
protocol = "wss://";
|
||||
}
|
||||
const ws = new WebSocket(protocol + window.location.host + "/connect");
|
||||
|
||||
ws.onopen = function (event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
document.getElementById("content").textContent = event.data;
|
||||
return false;
|
||||
}
|
||||
|
||||
ws.onerror = function (event) {
|
||||
console.log("ERROR!");
|
||||
console.log(event);
|
||||
document.getElementById("error").textContent = event.toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
ws.onclose = function (event) {
|
||||
console.log("WS CLOSED!");
|
||||
console.log(event);
|
||||
document.getElementById("error").textContent = event.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<!--
|
||||
Copyright 2019 Google LLC
|
||||
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. -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/static/dashboard.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="error" style="background-color: #FAA"></pre>
|
||||
<pre id="content"></pre>
|
||||
</body>
|
||||
</html>
|
@ -1,137 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 updater provides the ability for concurrently running demo pieces to
|
||||
// update a shared json object.
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Updater is like a json object, with each field allowed to be updated
|
||||
// concurrently by a different process. After processing updates, Updater will
|
||||
// call a provided method with the json serialized value of all of its fields.
|
||||
type Updater struct {
|
||||
ctx context.Context
|
||||
children map[string]*json.RawMessage
|
||||
updates chan update
|
||||
set SetFunc
|
||||
}
|
||||
|
||||
// SetFunc serializes the value passed in into json and sets the associated field
|
||||
// to that value. If nil is passed (BUT NOT a nil value of an interface), the
|
||||
// field will be removed from the Updater's json object.
|
||||
type SetFunc func(v interface{})
|
||||
|
||||
// New creates an Updater. Set is called when fields update, using the json
|
||||
// sererialized value of Updater's tree. All updates after ctx is canceled are
|
||||
// ignored.
|
||||
func New(ctx context.Context, set func([]byte)) *Updater {
|
||||
f := func(v interface{}) {
|
||||
set([]byte(*forceMarshalJson(v)))
|
||||
}
|
||||
return NewNested(ctx, SetFunc(f))
|
||||
}
|
||||
|
||||
// NewNested creates an updater based on a field in another updater. This
|
||||
// allows for grouping of related demo pieces into a single conceptual group.
|
||||
func NewNested(ctx context.Context, set SetFunc) *Updater {
|
||||
u := create(ctx, set)
|
||||
go u.start()
|
||||
return u
|
||||
}
|
||||
|
||||
func create(ctx context.Context, set SetFunc) *Updater {
|
||||
return &Updater{
|
||||
ctx: ctx,
|
||||
children: make(map[string]*json.RawMessage),
|
||||
updates: make(chan update),
|
||||
set: set,
|
||||
}
|
||||
}
|
||||
|
||||
// ForField returns a function to set the latest value of that demo piece.
|
||||
func (u *Updater) ForField(field string) SetFunc {
|
||||
return SetFunc(func(v interface{}) {
|
||||
var r *json.RawMessage
|
||||
if v != nil {
|
||||
r = forceMarshalJson(v)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
case u.updates <- update{field, r}:
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (u *Updater) start() {
|
||||
for {
|
||||
u.set(u.children)
|
||||
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
u.set(nil)
|
||||
return
|
||||
case up := <-u.updates:
|
||||
if up.value == nil {
|
||||
delete(u.children, up.field)
|
||||
} else {
|
||||
u.children[up.field] = up.value
|
||||
}
|
||||
}
|
||||
|
||||
applyAllWaitingUpdates:
|
||||
for {
|
||||
select {
|
||||
case up := <-u.updates:
|
||||
if up.value == nil {
|
||||
delete(u.children, up.field)
|
||||
} else {
|
||||
u.children[up.field] = up.value
|
||||
}
|
||||
default:
|
||||
break applyAllWaitingUpdates
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type update struct {
|
||||
field string
|
||||
value *json.RawMessage
|
||||
}
|
||||
|
||||
// forceMarshalJson is like json.Marshal, but cannot fail. It will instead
|
||||
// encode any error encountered into the json object on the field Error.
|
||||
func forceMarshalJson(v interface{}) *json.RawMessage {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
e := struct {
|
||||
Error string
|
||||
}{
|
||||
err.Error(),
|
||||
}
|
||||
|
||||
b, err = json.Marshal(e)
|
||||
if err != nil {
|
||||
b = []byte("{\"Error\":\"There was an error encoding the json message, additional there was an error encoding that error message.\"}")
|
||||
}
|
||||
}
|
||||
|
||||
r := json.RawMessage(b)
|
||||
return &r
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpdater(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
latest := make(chan string)
|
||||
|
||||
base := New(ctx, func(b []byte) {
|
||||
latest <- string(b)
|
||||
})
|
||||
|
||||
l := <-latest
|
||||
if l != "{}" {
|
||||
t.Errorf("Got %s, expected %s", l, "{}")
|
||||
}
|
||||
|
||||
child := NewNested(ctx, base.ForField("Foo"))
|
||||
|
||||
l = <-latest
|
||||
if l != "{\"Foo\":{}}" {
|
||||
t.Errorf("Got %s, expected %s", l, "{\"Foo\":{}}")
|
||||
}
|
||||
|
||||
child.ForField("Bar")(interface{}((*int)(nil)))
|
||||
|
||||
l = <-latest
|
||||
if l != "{\"Foo\":{\"Bar\":null}}" {
|
||||
t.Errorf("Got %s, expected %s", l, "{\"Foo\":{\"Bar\":null}}")
|
||||
}
|
||||
|
||||
child.ForField("Bar")(nil)
|
||||
|
||||
l = <-latest
|
||||
if l != "{\"Foo\":{}}" {
|
||||
t.Errorf("Got %s, expected %s", l, "{\"Foo\":{}}")
|
||||
}
|
||||
}
|
||||
|
||||
// Fully testing the updater's logic is difficult because it combines multiple
|
||||
// calls. This test method creates 100 different go routines all trying to
|
||||
// update a value to force the logic to be invoked.
|
||||
func TestUpdaterInternal(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(100)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
latest := ""
|
||||
|
||||
set := SetFunc(func(v interface{}) {
|
||||
if v != nil {
|
||||
latest = string(*forceMarshalJson(v))
|
||||
}
|
||||
})
|
||||
|
||||
u := create(ctx, set)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
set := u.ForField(strconv.Itoa(i))
|
||||
go func() {
|
||||
set("Hi")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Blocking call ensures that canceling the context will clean up the internal go routine.
|
||||
u.start()
|
||||
|
||||
expectedMap := make(map[string]string)
|
||||
for i := 0; i < 100; i++ {
|
||||
expectedMap[strconv.Itoa(i)] = "Hi"
|
||||
}
|
||||
// Not using forceMashal because it by design hides errors, and is used in the
|
||||
// code being tested.
|
||||
expectedB, err := json.Marshal(expectedMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := string(expectedB)
|
||||
|
||||
if latest != expected {
|
||||
t.Errorf("latest value is wrong. Expected '%s', got '%s'", expected, latest)
|
||||
}
|
||||
}
|
||||
|
||||
var marshalTests = []struct {
|
||||
in interface{}
|
||||
out string
|
||||
}{
|
||||
{map[string]int{"hi": 1}, "{\"hi\":1}"},
|
||||
{make(chan int), "{\"Error\":\"json: unsupported type: chan int\"}"},
|
||||
}
|
||||
|
||||
func TestForceMarshalJson(t *testing.T) {
|
||||
for _, tt := range marshalTests {
|
||||
t.Run(tt.out, func(t *testing.T) {
|
||||
s := string(*forceMarshalJson(tt.in))
|
||||
if s != tt.out {
|
||||
t.Errorf("got %s, want %s", s, tt.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
23
examples/evaluators/golang/serving/Dockerfile
Normal file
23
examples/evaluators/golang/serving/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/evaluators/golang/serving/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/examples/evaluators/golang/serving/serving .
|
||||
|
||||
ENTRYPOINT ["/serving"]
|
125
examples/evaluators/golang/serving/main.go
Normal file
125
examples/evaluators/golang/serving/main.go
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
This is a sample Evaluator built using the Evaluator Harness. It evaluates
|
||||
multiple proposals and approves a subset of them. This sample demonstrates
|
||||
how to build a basic Evaluator using the Evaluator Harness . This example
|
||||
over-simplifies the actual evaluation decisions and hence should not be
|
||||
used as is for a real scenario.
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
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
|
||||
|
||||
https://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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
harness "github.com/GoogleCloudPlatform/open-match/internal/harness/evaluator/golang"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This invoke the harness to set up the Evaluator. The harness abstracts
|
||||
// fetching proposals ready for evaluation and the process of transforming
|
||||
// approved proposals into results that Open Match can relay to the caller
|
||||
// requesting for matches.
|
||||
harness.RunEvaluator(Evaluate)
|
||||
}
|
||||
|
||||
// Evaluate is where your custom evaluation logic lives.
|
||||
// Input:
|
||||
// - proposals : List of all the proposals to be consiered for evaluation. Each proposal will have
|
||||
// Rosters comprising of the players belonging to that proposal.
|
||||
// Output:
|
||||
// - (proposals) : List of approved proposal IDs that can be returned as match results.
|
||||
func Evaluate(ctx context.Context, proposals []*pb.MatchObject) ([]string, error) {
|
||||
// Map of approved and overloaded proposals. Using maps for easier lookup.
|
||||
approvedProposals := map[string]bool{}
|
||||
overloadedProposals := map[string]bool{}
|
||||
|
||||
// Map of all the players encountered in the proposals. Each entry maps a player id to
|
||||
// the first match in which the player was encountered.
|
||||
allPlayers := map[string]string{}
|
||||
|
||||
// Iterate over each proposal to either add to approved map or overloaded map.
|
||||
for _, proposal := range proposals {
|
||||
proposalID := proposal.Id
|
||||
approved := true
|
||||
players := getPlayersInProposal(proposal)
|
||||
// Iterate over each player in the proposal to check if the player was encountered before.
|
||||
for _, playerID := range players {
|
||||
if propID, found := allPlayers[playerID]; found {
|
||||
// Player was encountered in an earlier proposal. Mark the current proposal as overloaded (not approved).
|
||||
// Also, the first proposal where the player was encountered may have been marked approved. Remove that proposal
|
||||
// approved proposals and add to overloaded proposals since we encountered its player in current proposal too.
|
||||
approved = false
|
||||
delete(approvedProposals, propID)
|
||||
overloadedProposals[propID] = true
|
||||
} else {
|
||||
// Player encountered for the first time, add to all players map with the current proposal.
|
||||
allPlayers[playerID] = proposalID
|
||||
}
|
||||
|
||||
if approved {
|
||||
approvedProposals[proposalID] = true
|
||||
} else {
|
||||
overloadedProposals[proposalID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the maps to lists of overloaded, approved proposals.
|
||||
overloadedList := []string{}
|
||||
approvedList := []string{}
|
||||
for k := range overloadedProposals {
|
||||
overloadedList = append(overloadedList, k)
|
||||
}
|
||||
|
||||
for k := range approvedProposals {
|
||||
approvedList = append(approvedList, k)
|
||||
}
|
||||
|
||||
// Select proposals to approve from the overloaded proposals list.
|
||||
chosen, err := chooseProposals(overloadedList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to select approved list from overloaded proposals, %v", err)
|
||||
}
|
||||
|
||||
// Add the chosen proposals to the approved list.
|
||||
approvedList = append(approvedList, chosen...)
|
||||
return approvedList, nil
|
||||
}
|
||||
|
||||
// chooseProposals should look through all overloaded proposals (that is, have a player that is also
|
||||
// in another proposed match) and choose the proposals to approve. This is where the core evaluation
|
||||
// logic will be added.
|
||||
func chooseProposals(overloaded []string) ([]string, error) {
|
||||
// As a basic example, we pick the first overloaded proposal for approval.
|
||||
approved := []string{}
|
||||
if len(overloaded) > 0 {
|
||||
approved = append(approved, overloaded[0])
|
||||
}
|
||||
return approved, nil
|
||||
}
|
||||
|
||||
func getPlayersInProposal(proposal *pb.MatchObject) []string {
|
||||
var players []string
|
||||
for _, r := range proposal.Rosters {
|
||||
for _, p := range r.Players {
|
||||
players = append(players, p.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return players
|
||||
}
|
23
examples/functions/golang/grpc-serving/Dockerfile
Normal file
23
examples/functions/golang/grpc-serving/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/functions/golang/grpc-serving
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o matchfunction .
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/examples/functions/golang/grpc-serving/matchfunction .
|
||||
|
||||
ENTRYPOINT ["/matchfunction"]
|
136
examples/functions/golang/grpc-serving/main.go
Normal file
136
examples/functions/golang/grpc-serving/main.go
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
This is a sample match function that uses the GRPC harness to set up
|
||||
the match making function as a service. This sample is a reference
|
||||
to demonstrate the usage of the GRPC harness and should only be used as
|
||||
a starting point for your match function. You will need to modify the
|
||||
matchmaking logic in this function based on your game's requirements.
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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
|
||||
|
||||
https://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 (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
harness "github.com/GoogleCloudPlatform/open-match/internal/harness/matchfunction/golang"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Invoke the harness to setup a GRPC service that handles requests to run the
|
||||
// match function. The harness itself queries open match for player pools for
|
||||
// the specified request and passes the pools to the match function to generate
|
||||
// proposals.
|
||||
harness.ServeMatchFunction(&harness.HarnessParams{
|
||||
FunctionName: "simple-matchfunction",
|
||||
ServicePortConfigName: "api.functions.port",
|
||||
ProxyPortConfigName: "api.functions.proxyport",
|
||||
Func: makeMatches,
|
||||
})
|
||||
}
|
||||
|
||||
// makeMatches is where your custom matchmaking logic lives.
|
||||
// Input:
|
||||
// - profile : 'Properties' of a MatchObject specified in the ListMatches or CreateMatch call.
|
||||
// - rosters : An array of Rosters. By convention, your input Roster contains players already in
|
||||
// the match, and the names of pools to search when trying to fill an empty slot.
|
||||
// - pools : An array PlayerPool messages. Contains all the players returned by the MMLogic API
|
||||
// upon querying for the player pools. It already had ignorelists applied as of the
|
||||
// time the player pool was queried.
|
||||
// Output:
|
||||
// - (results) JSON blob to populated in the MatchObject 'Properties' field sent to the ListMatches
|
||||
// or CreateMatch call.
|
||||
// - (rosters) Populated team rosters. Use is optional but recommended;
|
||||
// you'll need to construct at least one Roster with all the players you're selecting
|
||||
// as it is used to add those players to the ignorelist. You'll also need all
|
||||
// the players you want to assign to your DGS in Roster(s) when you call the
|
||||
// BackendAPI CreateAssignments() endpoint. Might as well put them in rosters now.
|
||||
// - error : Use if you need to return an unrecoverable error.
|
||||
func makeMatches(ctx context.Context, logger *log.Entry, profile string, rosters []*pb.Roster, pools []*pb.PlayerPool) (string, []*pb.Roster, error) {
|
||||
|
||||
// Open Match will try to marshal your JSON roster to an array of protobuf Roster objects. It's
|
||||
// up to you if you want to fill these protobuf Roster objects or just write your Rosters in your
|
||||
// custom JSON blob. This example uses the protobuf Rosters.
|
||||
|
||||
// Used for tracking metrics.
|
||||
var selectedPlayerCount int64
|
||||
|
||||
// Loop through all the team rosters sent in the call to create a match.
|
||||
for ti, team := range rosters {
|
||||
logger.Infof(" Attempting to fill team: %v", team.Name)
|
||||
|
||||
// Loop through all the players slots on this team roster.
|
||||
for si, slot := range team.Players {
|
||||
|
||||
// Loop through all the pools and check if there is a pool with a matching name to the
|
||||
// poolName for this player slot. Just one example of a way for your matchmaker to
|
||||
// specify which pools your MMF should search through to fill a given player slot.
|
||||
// Optional, feel free to change as you see fit.
|
||||
for _, pool := range pools {
|
||||
if slot.Pool == pool.Name && len(pool.Roster.Players) > 0 {
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// These next few lines are where you would put your custom logic, such as
|
||||
// searching the pool for players with similar latencies or skill ratings
|
||||
// to the players you have already selected. This example doesn't do anything
|
||||
// but choose at random!
|
||||
logger.Infof("Looking for player in pool: %v, size: %v", pool.Name, len(pool.Roster.Players))
|
||||
randPlayerIndex := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(pool.Roster.Players))
|
||||
|
||||
// Get random player with this index
|
||||
selectedPlayer := pool.Roster.Players[randPlayerIndex]
|
||||
logger.Infof("Selected player index %v: %v", randPlayerIndex, selectedPlayer.Id)
|
||||
|
||||
// Remove this player from the array as they are now used up.
|
||||
pool.Roster.Players[randPlayerIndex] = pool.Roster.Players[0]
|
||||
|
||||
// This is a functional pop from a set.
|
||||
_, pool.Roster.Players = pool.Roster.Players[0], pool.Roster.Players[1:]
|
||||
|
||||
// Write the player to the slot and loop.
|
||||
rosters[ti].Players[si] = selectedPlayer
|
||||
selectedPlayerCount++
|
||||
break
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
} else {
|
||||
// Weren't enough players left in the pool to fill all the slots so this example errors out.
|
||||
// For this example, this is an error condition and so the match result will have the error
|
||||
// populated. If your game can handle partial results, customize this to NOT return an error
|
||||
// and instaead populate the result with any properties that may be needed to evaluate the proposal.
|
||||
return "", rosters, errors.New("insufficient players")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info(" Rosters complete.")
|
||||
|
||||
// You can send back any arbitrary JSON in the first return value (the 'results' string). It
|
||||
// will get sent back out the backend API in the Properties field of the MatchObject message.
|
||||
// In this example, all the selected players are populated to the Rosters array, so we'll just
|
||||
// pass back the input profile back as the results. If there was anything else arbitrary as
|
||||
// output from the MMF, it could easily be included here.
|
||||
results := profile
|
||||
|
||||
logger.Infof("Selected %v players", selectedPlayerCount)
|
||||
return results, rosters, nil
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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 open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match/examples/functions/golang/soloduel
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o matchfunction .
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /app/
|
||||
COPY --from=builder --chown=nonroot /go/src/open-match.dev/open-match/examples/functions/golang/soloduel/matchfunction /app/
|
||||
|
||||
ENTRYPOINT ["/app/matchfunction"]
|
@ -1,33 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 defines a sample match function that uses the GRPC harness to set up
|
||||
// the match making function as a service. This sample is a reference
|
||||
// to demonstrate the usage of the GRPC harness and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples/functions/golang/soloduel/mmf"
|
||||
)
|
||||
|
||||
const (
|
||||
queryServiceAddr = "open-match-query.open-match.svc.cluster.local:50503" // Address of the QueryService endpoint.
|
||||
serverPort = 50502 // The port for hosting the Match Function.
|
||||
)
|
||||
|
||||
func main() {
|
||||
mmf.Start(queryServiceAddr, serverPort)
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 mmf provides a sample match function that uses the GRPC harness to set up 1v1 matches.
|
||||
// This sample is a reference to demonstrate the usage of the GRPC harness and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/pkg/matchfunction"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
var (
|
||||
matchName = "a-simple-1v1-matchfunction"
|
||||
)
|
||||
|
||||
// matchFunctionService implements pb.MatchFunctionServer, the server generated
|
||||
// by compiling the protobuf, by fulfilling the pb.MatchFunctionServer interface.
|
||||
type matchFunctionService struct {
|
||||
grpc *grpc.Server
|
||||
queryServiceClient pb.QueryServiceClient
|
||||
port int
|
||||
}
|
||||
|
||||
func makeMatches(poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
tickets := map[string]*pb.Ticket{}
|
||||
for _, pool := range poolTickets {
|
||||
for _, ticket := range pool {
|
||||
tickets[ticket.GetId()] = ticket
|
||||
}
|
||||
}
|
||||
|
||||
var matches []*pb.Match
|
||||
|
||||
t := time.Now().Format("2006-01-02T15:04:05.00")
|
||||
|
||||
thisMatch := make([]*pb.Ticket, 0, 2)
|
||||
matchNum := 0
|
||||
|
||||
for _, ticket := range tickets {
|
||||
thisMatch = append(thisMatch, ticket)
|
||||
|
||||
if len(thisMatch) >= 2 {
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%s-time-%s-num-%d", matchName, t, matchNum),
|
||||
MatchProfile: matchName,
|
||||
MatchFunction: matchName,
|
||||
Tickets: thisMatch,
|
||||
})
|
||||
|
||||
thisMatch = make([]*pb.Ticket, 0, 2)
|
||||
matchNum++
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// Run is this match function's implementation of the gRPC call defined in api/matchfunction.proto.
|
||||
func (s *matchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error {
|
||||
// Fetch tickets for the pools specified in the Match Profile.
|
||||
log.Printf("Generating proposals for function %v", req.GetProfile().GetName())
|
||||
|
||||
poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools())
|
||||
if err != nil {
|
||||
log.Printf("Failed to query tickets for the given pools, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate proposals.
|
||||
proposals, err := makeMatches(poolTickets)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate matches, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Streaming %v proposals to Open Match", len(proposals))
|
||||
// Stream the generated proposals back to Open Match.
|
||||
for _, proposal := range proposals {
|
||||
if err := stream.Send(&pb.RunResponse{Proposal: proposal}); err != nil {
|
||||
log.Printf("Failed to stream proposals to Open Match, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 mmf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMakeMatchesDeduplicate(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
poolNameToTickets := map[string][]*pb.Ticket{
|
||||
"pool1": {{Id: "1"}},
|
||||
"pool2": {{Id: "1"}},
|
||||
}
|
||||
|
||||
matches, err := makeMatches(poolNameToTickets)
|
||||
require.Nil(err)
|
||||
require.Equal(len(matches), 0)
|
||||
}
|
||||
|
||||
func TestMakeMatches(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
poolNameToTickets := map[string][]*pb.Ticket{
|
||||
"pool1": {{Id: "1"}, {Id: "2"}, {Id: "3"}},
|
||||
"pool2": {{Id: "4"}},
|
||||
"pool3": {{Id: "5"}, {Id: "6"}, {Id: "7"}},
|
||||
}
|
||||
|
||||
matches, err := makeMatches(poolNameToTickets)
|
||||
require.Nil(err)
|
||||
require.Equal(len(matches), 3)
|
||||
|
||||
for _, match := range matches {
|
||||
require.Equal(2, len(match.Tickets))
|
||||
require.Equal(matchName, match.MatchFunction)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user