mirror of
https://github.com/googleforgames/open-match.git
synced 2025-04-04 12:09:58 +00:00
Compare commits
871 Commits
v0.3.3-alp
...
release-1.
Author | SHA1 | Date | |
---|---|---|---|
c1458e3127 | |||
c58996da62 | |||
8474eca3cc | |||
2ac12b1c83 | |||
dbda6c8dc1 | |||
98e7a02ebf | |||
3d1bae2021 | |||
7070f056df | |||
4c2544f043 | |||
2e6aa4f36f | |||
50b4063bee | |||
31a4a45d73 | |||
67be35006c | |||
d0ce1b317f | |||
93cd5c7a9f | |||
3193921816 | |||
7a3bb82089 | |||
a4eb6d6cbd | |||
927a976a10 | |||
33efc848ff | |||
04c019c6cb | |||
1e51ad859c | |||
fdd8783a34 | |||
036be6455d | |||
5d5f4de7a7 | |||
a9f985d217 | |||
6598a55e74 | |||
4d6da1632a | |||
40a06447d0 | |||
a9d122f50c | |||
73ec73f2e8 | |||
361f8ff3db | |||
8297cac2b8 | |||
120a114647 | |||
7af54ee1bc | |||
68cecb91e5 | |||
67dc60dba8 | |||
09d1ff7171 | |||
b6e5114715 | |||
23d2fd5042 | |||
2b73d52e0c | |||
47c34587dc | |||
76937b6350 | |||
2e03c1a197 | |||
eca40e3298 | |||
902c9d69b4 | |||
67767cf1cd | |||
6f46731b15 | |||
0d1a77c5de | |||
f2a23f5ba1 | |||
3fa588c1f8 | |||
cc08f39205 | |||
ec9cf00bcf | |||
8b8617f68d | |||
ce9b989e58 | |||
5c00395c78 | |||
faf3eded1f | |||
250d44aefd | |||
13fdf5960f | |||
aa5a1f9da1 | |||
ad1ca16218 | |||
7d849f3f04 | |||
05c8c8aa76 | |||
f50c9eec80 | |||
c6f23f01ca | |||
21efdb6691 | |||
81a1dc38b6 | |||
d0ddf22658 | |||
ee247c6c1a | |||
a17eb3bc72 | |||
3d194f541e | |||
3a0cd7611b | |||
c13b461795 | |||
b9e55fc727 | |||
dd1386a55b | |||
defac9065b | |||
f203384fbf | |||
7ef9c052bd | |||
ea744b8b51 | |||
1a8fc62833 | |||
1d5574b8a3 | |||
75a3d43477 | |||
252fc8090d | |||
2c617f2cb6 | |||
fcd590eca6 | |||
4b3147511b | |||
c85af44567 | |||
688262111d | |||
26d1aa236a | |||
fff37cd82c | |||
98a227b515 | |||
88cd95fe57 | |||
248494c04c | |||
aa4398e786 | |||
fc5c3629e8 | |||
8d86709632 | |||
0a273674b9 | |||
e2247a7f53 | |||
b269896c23 | |||
a210185098 | |||
4df95deb54 | |||
a9b8eec9e0 | |||
afa59327a4 | |||
d86b6c5121 | |||
2eb2921914 | |||
80d882b7c7 | |||
0f34e31778 | |||
d45eb74510 | |||
1765ab7b7e | |||
6f05e526fb | |||
496d156faa | |||
3a3d618c43 | |||
e1cbd855f5 | |||
10b36705f0 | |||
a6fc4724bc | |||
511337088a | |||
5f67bb36a6 | |||
94d2105809 | |||
d85f1f4bc7 | |||
79e9afeca7 | |||
3334f7f74a | |||
85ce954eb9 | |||
679cfb5839 | |||
c53a5b7c88 | |||
cfb316169a | |||
a9365b5333 | |||
93df53201c | |||
eb86841423 | |||
771f706317 | |||
a9f9a2f2e6 | |||
068632285e | |||
113461114e | |||
0ac7ae13ac | |||
29a2dbcf99 | |||
48d3b5c0ee | |||
a5fa651106 | |||
cd84d74ff9 | |||
8c2aa1ea81 | |||
493ff8e520 | |||
8363bc5fc9 | |||
144f646b7f | |||
b518b5cc1b | |||
af0b9fd5f7 | |||
5f4b522ecd | |||
12625d7f53 | |||
3248c8c4ad | |||
10c0c59997 | |||
c17e3e62c0 | |||
8e91be6201 | |||
f6c837d6cd | |||
3c8908aae0 | |||
0689d92d9c | |||
3c9a8f5568 | |||
30204a2d15 | |||
a5b6c0c566 | |||
4a00baf847 | |||
d74262f3ba | |||
2262652ea9 | |||
e15fd47535 | |||
670f38d36e | |||
f0a85633a5 | |||
6cb47ce191 | |||
529c01330e | |||
b36a348db7 | |||
5e277265ad | |||
4420d7add2 | |||
3de052279b | |||
7a4aa3589f | |||
bca6f487cc | |||
d0c373a850 | |||
deb2947ae2 | |||
d889278151 | |||
1b63fa53dc | |||
af02e4818f | |||
cda2d3185f | |||
2317977602 | |||
9ef83ed344 | |||
33bd633b1d | |||
1af8cf1e79 | |||
0ef46fc4d4 | |||
79daf50531 | |||
a9c327b430 | |||
2c637c97b8 | |||
668b10030b | |||
1c7fd24a34 | |||
be0cebd457 | |||
fe7bb4da8f | |||
e80de171a0 | |||
fdd707347e | |||
6ef1382414 | |||
d67a65e648 | |||
d3e008cd1e | |||
d93db94ad9 | |||
1bd63a01c7 | |||
cf8d49052c | |||
fca5359eee | |||
07637135a9 | |||
8c86a4e643 | |||
31858e0ce5 | |||
fc0b6dc510 | |||
edade67a6d | |||
c92c4ef07a | |||
0b8425184b | |||
338a03cce5 | |||
b7850ab81d | |||
faa730bda8 | |||
76ef9546af | |||
bff8934cd3 | |||
3a5608b547 | |||
b7eec77a36 | |||
82a011ea52 | |||
92210b1a13 | |||
f46c0b8f3d | |||
a19baf3457 | |||
8e1fbaf938 | |||
957471cf83 | |||
e24c4b9884 | |||
34cc4987e8 | |||
8e8f2d688b | |||
f347639df4 | |||
75c74681cb | |||
5b18dcf6f3 | |||
3bcf327a41 | |||
9f59844e0d | |||
5a32cef2e9 | |||
b9e2e88ef4 | |||
41632e6b8d | |||
188457c21f | |||
4daea744d5 | |||
1f3dd4bcbf | |||
d82fc4fec6 | |||
8cb43950a1 | |||
9934a7e9da | |||
8db449b307 | |||
b78d4672a6 | |||
e048b97c71 | |||
f56263b074 | |||
aaca99c211 | |||
9c1b0bcc0e | |||
80675c32f6 | |||
4e408b1abc | |||
fd4f154a0e | |||
3e2d20edc0 | |||
40ba558eb2 | |||
72bcd72d5c | |||
b276ed1a08 | |||
d977486dc5 | |||
1f74497bdd | |||
57e9540faa | |||
a0be7dcec5 | |||
391cc4dc72 | |||
2c8779c5d7 | |||
e5aafc5ed7 | |||
8554601a70 | |||
a75833b85a | |||
f01105995d | |||
f949de7dce | |||
335bf73904 | |||
7a1dcbdf93 | |||
0a65bdefe5 | |||
bcf0e6b9fb | |||
1f5df7abef | |||
7005d40939 | |||
3536913559 | |||
103213f940 | |||
3b8efce53d | |||
580ed235d7 | |||
23cc35ae68 | |||
c002e75fde | |||
6e6f063958 | |||
8d31b5af07 | |||
f1a5cd9b81 | |||
d3d906c8be | |||
6068507370 | |||
04b06fcf90 | |||
0c25ac9139 | |||
0565a014ad | |||
57e59c3821 | |||
608d5bce71 | |||
52b8754eb8 | |||
a10817f550 | |||
817a0968e7 | |||
043a984bab | |||
02d8d1f1fe | |||
242d799c18 | |||
33189f9154 | |||
9ef7cb6277 | |||
b05c9f5574 | |||
8a29f15fe0 | |||
5fa0cc700c | |||
d579de63aa | |||
797352a3fc | |||
68882a79bb | |||
11bf81e146 | |||
02aa992ac7 | |||
f5b651669c | |||
b9522a8bb5 | |||
8c6fbcbe49 | |||
99141686c9 | |||
3cf9c2ad6a | |||
755c0e82f1 | |||
18bc9f31fd | |||
05325d3b77 | |||
3c7f73ed03 | |||
525d35b341 | |||
d3e8638a3b | |||
af19404eef | |||
2f9e1c2209 | |||
669f7d63b7 | |||
8740494f3e | |||
3899bd2fcd | |||
dac6ac141e | |||
c859e04bf9 | |||
7a48467cb5 | |||
74992cdf79 | |||
dd21919c00 | |||
031b39e9c2 | |||
3f8f858d85 | |||
1dbd3a5a45 | |||
fc94a7c451 | |||
60d20ebae5 | |||
e369ac3c0b | |||
2c35ecb304 | |||
7350524e78 | |||
2aee5d128d | |||
4773b7b7cf | |||
1abdace01e | |||
bbcf8d47b4 | |||
d65dee6be0 | |||
02e6b3bbde | |||
77090d1a5b | |||
ce3a7bf389 | |||
bcf710b755 | |||
2b7eec8c07 | |||
99164df2db | |||
efa1ce5a0b | |||
cb610a92b1 | |||
89691b5512 | |||
252d473d72 | |||
56cfb8e66e | |||
5f32d4b765 | |||
658aee8874 | |||
6ac7910fb1 | |||
fcf7c81c84 | |||
e3d630729c | |||
da9d48ddb1 | |||
3727b0d5d8 | |||
cc1b70dd2e | |||
91090af431 | |||
6661df62ae | |||
f63a93b139 | |||
31648c35f3 | |||
e96a6e8af7 | |||
d9912c3e28 | |||
8933255ec2 | |||
3617d3cdbd | |||
736a979b47 | |||
aa99d7860e | |||
22ad6fed6b | |||
39e495512b | |||
291e60a735 | |||
b29dfae9cf | |||
377b40041d | |||
6f7b7640c2 | |||
039eefb690 | |||
5de0ae1fc4 | |||
1e5560603a | |||
61449fe2cf | |||
21cf0697fe | |||
12e5a37816 | |||
e658cc0d84 | |||
9e89735d79 | |||
5cbbfef1cc | |||
1c0e4ff94e | |||
79862c9950 | |||
8ac27d7975 | |||
86b8cb5aa8 | |||
fdea3c8f1e | |||
61a28df3e5 | |||
13fe3fe5a9 | |||
a674fb1c02 | |||
75ffc83b98 | |||
7dc4de6a14 | |||
f02283e2a6 | |||
d1fe7f1ac4 | |||
84eb9b27ef | |||
707de22912 | |||
780e3abf10 | |||
524b7d333f | |||
c544b9a239 | |||
04b6f1a5ad | |||
13952ea54e | |||
a61f4a643e | |||
949fa28505 | |||
85cc481f5d | |||
c3cbcd7625 | |||
e01fc12549 | |||
e1682100fa | |||
603aef207f | |||
baf403ac44 | |||
b1da77eaba | |||
bb82a397d2 | |||
abd2c1434c | |||
bc9dc27210 | |||
084461d387 | |||
bc7d014db6 | |||
230ae76bb4 | |||
ebbe5aa6ce | |||
9b350c690c | |||
80b817f488 | |||
df7021de1b | |||
5c8f218000 | |||
3f538df971 | |||
1e856658c9 | |||
eb6697052d | |||
31d3464a31 | |||
c96b65d52b | |||
9d601351cc | |||
7272ca8b93 | |||
b463d2e0fd | |||
07da543f8e | |||
0d54c39828 | |||
5469c8bc69 | |||
c837211cd1 | |||
5729e72214 | |||
66910632da | |||
c832074112 | |||
a6d526b36b | |||
13e017ba65 | |||
3784300d22 | |||
31fd18e39b | |||
a54d1fcf21 | |||
72a435758e | |||
6848fa71c2 | |||
987d90cc44 | |||
baf943fdd3 | |||
c7ce1b047b | |||
36a194e761 | |||
605511d177 | |||
2a08732508 | |||
e1c2b96cb5 | |||
1bd84355b7 | |||
a9014fbf78 | |||
8050c61618 | |||
8b765871c4 | |||
88786ecbd1 | |||
f41c175f29 | |||
3607809371 | |||
d21ae712a7 | |||
f3f1908318 | |||
9cb4a9ce6e | |||
8dad7fd7d0 | |||
f70cfee14a | |||
36f92b4336 | |||
164dfdde67 | |||
c0d6531f3f | |||
52610974de | |||
041572eef6 | |||
e28fe42f3b | |||
880e340859 | |||
88a659544e | |||
9381918163 | |||
1c41052bd6 | |||
819ae18478 | |||
ad96f42b94 | |||
1d778c079c | |||
4bbfafd761 | |||
28e5d0a1d1 | |||
a394c8b22e | |||
93276f4d02 | |||
310d98a078 | |||
a84eda4dab | |||
ce038bc6dd | |||
74fb195f41 | |||
1dc3fc8b6b | |||
de469cb349 | |||
7462f32125 | |||
3268461a21 | |||
3897cd295e | |||
5a9212a46e | |||
04cfddefd0 | |||
b7872489ae | |||
6d65841b77 | |||
b4fb725008 | |||
50d9a0c234 | |||
a68fd5ed1e | |||
be58fae864 | |||
f22ad9afc5 | |||
6b1b84c54e | |||
043ffd69e3 | |||
e5f7d3bafe | |||
8f88ba151e | |||
9c83062a41 | |||
7b31bdcedf | |||
269e6cd0ad | |||
864f13f2e8 | |||
e3a9f59ad9 | |||
8a3f6e43b8 | |||
2b8597c72e | |||
c403f28c04 | |||
317f914daa | |||
16fbc015b2 | |||
d1d9114ddb | |||
e6622ff585 | |||
99fb4a8fcf | |||
e0ebb139bf | |||
31dcbe39f7 | |||
76a1cd8427 | |||
ac6c00c89d | |||
a7d97fdf0d | |||
f08121cf25 | |||
cd6dd410ee | |||
5f0a2409e8 | |||
d445a0b2d5 | |||
1526827e3c | |||
82e60e861f | |||
5900a1c542 | |||
a02aa99c7a | |||
2f3f8b7f56 | |||
a7eb1719cc | |||
ea24b702c8 | |||
e7ab30dc63 | |||
8b88f26e4e | |||
d5f60ae202 | |||
113ee00a6c | |||
c083f1735a | |||
52ad8de602 | |||
3daebfc39d | |||
3e5da9f7d5 | |||
951e82b6a2 | |||
d201242610 | |||
1328a109e5 | |||
2415194e68 | |||
b2214f7b9b | |||
98220fdc0b | |||
b2bf00631a | |||
49ac68c32a | |||
7b3d6d38d3 | |||
a1271ff820 | |||
2932144d80 | |||
3a14bf3641 | |||
7d0ec363e5 | |||
dcff6326b1 | |||
ffd77212b0 | |||
ea3e529b0d | |||
db2c298a48 | |||
85d5f9fdbb | |||
401329030a | |||
d9e20f9c29 | |||
f95164148f | |||
ab39bcc93d | |||
d1ae3e9620 | |||
de83c9f06a | |||
9fb445fda6 | |||
050367eb88 | |||
40d288964b | |||
e4c87c2c3a | |||
bd2927bcc5 | |||
271e745a61 | |||
98c15e78ad | |||
fbbe3cd2b4 | |||
878ef89c40 | |||
a9a5a29e58 | |||
8cd7cd0035 | |||
92495071d2 | |||
a766b38d62 | |||
6c941909e8 | |||
77dc8f8c47 | |||
a804e1009b | |||
3b2efc39c7 | |||
b8054633bf | |||
336fad9079 | |||
ce59eedd29 | |||
83c0913c34 | |||
6b50cdd804 | |||
f427303505 | |||
269dd9bc2f | |||
d501dbcde6 | |||
04c4e376b5 | |||
3e61359f05 | |||
8275ed76c5 | |||
e8b2525262 | |||
3517b7725c | |||
6cd521abf7 | |||
924fccfeb3 | |||
c17ca7a10c | |||
b11863071f | |||
20dbcea99f | |||
13505956a0 | |||
3d04025860 | |||
d0f7f2c4d3 | |||
272e7642b1 | |||
f3f80a70bd | |||
80bcd9487f | |||
2e25daf474 | |||
9fe32eef96 | |||
0446159872 | |||
2ef8614687 | |||
de8279dfe0 | |||
8fedc2900f | |||
0f95adce20 | |||
4f851094a0 | |||
9cae854771 | |||
603089f404 | |||
d024b46487 | |||
a2616870c7 | |||
6d8b516026 | |||
b4d3e84e3d | |||
6b370f56c8 | |||
d5da3d16b7 | |||
31469bb0f9 | |||
e8adc57f76 | |||
0da8d0d221 | |||
08d9210588 | |||
8882d8c9a1 | |||
6c65e924ec | |||
beba937ac5 | |||
caa755272b | |||
ea60386fa0 | |||
9000ae8de4 | |||
4e2b64722f | |||
4c0f24217f | |||
872b7be6a5 | |||
53f2ee208f | |||
b5eaf153e8 | |||
d3c7eb2000 | |||
23243e2815 | |||
3be97908b2 | |||
5892f81214 | |||
40892c9b2e | |||
9691d3f001 | |||
9808066375 | |||
534716eef4 | |||
ece4a602d0 | |||
8eb72d98b2 | |||
6da8a34b67 | |||
b03189e34c | |||
9766871a87 | |||
4eac4cb29a | |||
439286523d | |||
e0058c7c08 | |||
ec40f26e62 | |||
17134f0a40 | |||
add2464b33 | |||
b72b4f9b54 | |||
abdc3aca28 | |||
3ab724e848 | |||
3c8d0ce1b0 | |||
c0166e3176 | |||
3623adb90e | |||
fba1bcf445 | |||
fdd865200a | |||
b0fc8f261f | |||
bdd3503d80 | |||
81fd2bab83 | |||
212a416789 | |||
2425e28129 | |||
3993a2f584 | |||
05cb4e503f | |||
1cf11e7d81 | |||
1985ecefed | |||
b7ebb60325 | |||
e4651d9382 | |||
04a574688a | |||
d4a901fc71 | |||
5de79f90cf | |||
e42c8a0232 | |||
1503ffae3a | |||
a842da5563 | |||
c3d6efef72 | |||
0516ab0800 | |||
668bfd6104 | |||
ef933ed6ef | |||
3ee24e3f28 | |||
d0bd794a61 | |||
37bbf470de | |||
412cb8557a | |||
38e81a9fd1 | |||
cb24baf107 | |||
c6f6526823 | |||
41e441050f | |||
235e25c748 | |||
93ca5e885c | |||
5d67fb1548 | |||
faa6e17607 | |||
6a0c648a8f | |||
8516e3e036 | |||
e476078b9f | |||
0d405df761 | |||
06c1208ab2 | |||
af335032a8 | |||
a8be8afce2 | |||
e524121b4b | |||
871abeee69 | |||
b9af86b829 | |||
6a9572c416 | |||
636eb07869 | |||
d56c983c17 | |||
a8e857c0ba | |||
75da6e1f4a | |||
e1fba5c1e8 | |||
d9911bdfdd | |||
175293fdf9 | |||
01407fbcad | |||
edad339a76 | |||
c57c841dfc | |||
54dc0e0518 | |||
fa4e8887d0 | |||
8384cb00b2 | |||
b9502a59a0 | |||
139d345915 | |||
5ea5b29af4 | |||
812afb2d06 | |||
cc82527eb5 | |||
80b20623fb | |||
a270eab4b4 | |||
36f7dcc242 | |||
a4706cbb73 | |||
c09cc8e27f | |||
be55bfd1e8 | |||
8389a62cf1 | |||
af8895e629 | |||
2a3241307f | |||
f777a4f407 | |||
88ca8d7b7c | |||
3a09ce142a | |||
8d8fdf0494 | |||
45b0a7c38e | |||
4cbee9d8a7 | |||
55afac2c93 | |||
8077dbcdba | |||
f1fc02755b | |||
0cce1745bc | |||
d57b5f1872 | |||
1355e5c79e | |||
4809f2801f | |||
68d323f3ea | |||
b99160e356 | |||
98d4c31c61 | |||
b4beb68920 | |||
b41a704886 | |||
88a692cdf3 | |||
623519bbb4 | |||
655abfbb26 | |||
ac81b74fad | |||
ba62520d9c | |||
0205186e6f | |||
ef2b1ea0a8 | |||
1fe2bd4900 | |||
5333ef2092 | |||
09b727b555 | |||
c542d6d1c3 | |||
8f3f7625ec | |||
6a4f309bd5 | |||
26f5426b61 | |||
f464b0bd7b | |||
092b7e634c | |||
454a3d6cca | |||
50e3ede4b9 | |||
6c36145e9b | |||
47644004db | |||
1dec4d7555 | |||
1c6f43a95f | |||
0cea4ed713 | |||
db912b2d68 | |||
726b1d4063 | |||
468aef3835 | |||
c6e257ae76 | |||
8e071020fa | |||
c032e8f382 | |||
2af432c3d7 | |||
4ddceb62ee | |||
ddb4521444 | |||
86918e69eb | |||
2d6db7e546 | |||
fc52ef6428 | |||
1bfb30be6f | |||
9ee341baf2 | |||
7869e6eb81 | |||
7edca56f56 | |||
eaedaa0265 | |||
9cc8312ff5 | |||
2f0a1ad05b | |||
2ff77ac90b | |||
2a3cfea505 | |||
b8326c2a91 | |||
ccc9d87692 | |||
bba49f3ec4 | |||
632157806f | |||
6e039cb797 | |||
8db062f7b9 | |||
f379a5eb46 | |||
f3160cfc5c | |||
442a1ff013 | |||
0fb75ab35e | |||
6308b218cc | |||
624ba5c018 | |||
82d034f8e4 | |||
97eed146da | |||
6dd23ff6ad | |||
03c7db7680 | |||
e5538401f6 | |||
eaa811f9ac | |||
3b1c6b9141 | |||
34f9eb9bd3 | |||
3ad7f75fb4 | |||
78bd48118d | |||
3e71894111 | |||
36decb4068 | |||
f79b782a3a | |||
db186e55ff | |||
957465ce51 | |||
478eb61589 | |||
6d2a5b743b | |||
9c943d5a10 | |||
8293d44ee0 | |||
a3bd862e76 | |||
c424d5eac9 | |||
2e6f5173e0 | |||
ee4bba44ec | |||
8e923a4328 | |||
52efa04ee6 | |||
67d4965648 | |||
7a7b1cb305 | |||
377a9621ff | |||
432dd5a504 | |||
7446f5b1eb | |||
15ea999628 | |||
b5367ea3aa | |||
e022c02cb6 | |||
a13455d5b0 | |||
16741409e7 | |||
d7e8f8b3fa | |||
8c97c8f141 | |||
6a8755a13d | |||
4ed6d275a3 | |||
cb49eb8646 | |||
a7458dabf2 | |||
5856b7d873 | |||
7733824c21 | |||
f1d261044b | |||
95820431ab | |||
0002ecbdb2 | |||
2eb51b5270 | |||
1847f79571 | |||
58ff12f3f8 | |||
b0b7b4bd15 | |||
f3f1f36099 | |||
f8cfb1b90f | |||
393e1d6de2 | |||
a11556433b | |||
3ee9c05db7 | |||
de7ba2db6b | |||
8393454158 | |||
6b93ac7663 | |||
fe2410e9d8 | |||
d8ecf1c439 | |||
8577f6bd4d | |||
470be06d16 | |||
c6e4dae79b | |||
dd794fd004 | |||
f234433e33 | |||
d52773543d | |||
bd4ab0b530 | |||
6b9cd11be3 | |||
1443bd1e80 | |||
3fd8081dc5 | |||
dda949a6a4 | |||
3fcedbf13b | |||
274edaae2e | |||
8ed865d300 |
137
.dockerignore
Normal file
137
.dockerignore
Normal file
@ -0,0 +1,137 @@
|
||||
# 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
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# vim swap files
|
||||
*swp
|
||||
*swo
|
||||
*~
|
||||
|
||||
# Ping data files
|
||||
*.ping
|
||||
*.pings
|
||||
*.population*
|
||||
*.percent
|
||||
*.cities
|
||||
populations
|
||||
|
||||
# Discarded code snippets
|
||||
build.sh
|
||||
*-fast.yaml
|
||||
detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
*.pyc
|
||||
nupkg/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
|
||||
# Goland
|
||||
.idea/
|
||||
|
||||
# Nodejs files placed when building Hugo, ok to allow if we actually start using Nodejs.
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
site/resources/_gen/
|
||||
|
||||
# Node Modules
|
||||
node_modules/
|
||||
|
||||
# Install YAML files, Helm is the source of truth for configuration.
|
||||
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
|
||||
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
|
||||
build/
|
||||
|
||||
# Secrets Directories
|
||||
install/helm/open-match/secrets/
|
||||
|
||||
# Helm tar charts
|
||||
install/helm/open-match/charts/
|
@ -1,3 +1,17 @@
|
||||
# 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
|
||||
@ -12,3 +26,4 @@
|
||||
# below:
|
||||
.git
|
||||
.gitignore
|
||||
#!include:.gitignore
|
||||
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @laremere @aLekSer @HazWard @calebatwd @syntxerror @sawagh @amg84 @scosgrave @mridulji @markmandel @joeholley @kazshinohara @kemurayama @govargo @ashutosji
|
25
.github/ISSUE_TEMPLATE/apichange.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/apichange.md
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
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
Normal file
30
.github/ISSUE_TEMPLATE/bugreport.md
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
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
Normal file
20
.github/ISSUE_TEMPLATE/featurerequest.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
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.
|
175
.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
175
.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
---
|
||||
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/main
|
||||
# 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.Z` 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 main 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.Z. Example: release-0.5. At this point there's effectively a code
|
||||
freeze for this version and all work on main 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.Z branch on [open-match-discuss@](https://groups.google.com/forum/#!forum/open-match-discuss).
|
||||
- [ ] 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.
|
||||
- [ ] Update usage requirements in the Installation doc - e.g. supported minikube version, kubectl version, golang version, etc.
|
||||
- [ ] 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.Z` 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. Note: the helm chart is not published to the public registry until the merge is complete (it's a second cloud build trigger upon merge), so you won't be able to do final release testing until after all checks/approvals are finished!
|
||||
|
||||
## 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/main/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.Z` (Example: release-0.5.)
|
||||
- [ ] `Release Title = v{version}` (Must match `Tag`)
|
||||
- [ ] `Write` section will contain the contents from the [release template](https://github.com/googleforgames/open-match/blob/main/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 main since this release" for the diff.
|
||||
- [ ] 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 everything in the [milestone](https://github.com/googleforgames/open-match/milestones) is complete with the exception of the release issue itself.
|
||||
|
||||
## Build And Test Artifacts
|
||||
|
||||
- [ ] Navigate to the [Cloud Console](https://console.cloud.google.com) in a browser and open the [Cloud Build History section](https://console.cloud.google.com/cloud-build/builds?project=open-match-build) and find the latest "Post Submit" build (trigger id: 9a451c7a-197b-4a38-a612-21f4c53c42fd) of the merged commit. The build may still be running, if so wait for it to finish. If it failed, fix the error and repeat this section. Open the build details and click on step 12, "Build: Docker Images". Take note of the docker image version tag near the top of the build log. This is the "{source version tag}" referenced in various commands below. 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 not a release candidate or preview but a full release, run `./docs/governance/templates/release.sh {source version tag} latest` to tag these public images as the default version to pull from the registry.
|
||||
- [ ] Once the images have successfully been pushed to the registry, modify the line `open-match.dev/open-match v0.0.0-dev` in all `go.mod` files in the [Tutorials] (https://github.com/googleforgames/open-match/tree/main/tutorials) directory to use the current release version for the remainder of your local release testing. This includes all solution subdirectories as well. This change is local only and doesn't get committed to git.
|
||||
- [ ] Copy the installation files named `{sequence_number}-{component}.yaml` (example: `01-open-match-core.yaml`) from the [build folder in the private open-match-build-artifacts GCS bucket https://storage.mtls.cloud.google.com/open-match-build-artifacts/{version}](https://console.cloud.google.com/storage/browser/open-match-build-artifacts?project=open-match-build) to the release draft you created. Download them to your local machine, and then attach them to the draft using the Github UI. Note: the `05-jaeger.yaml` file no longer exists after release 1.8, so don't be surprised if that number is missing.
|
||||
- [ ] Update the [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 the YAML files attached to the release and the latest Helm chart, pulled from the public helm repo (not your local copy from github). 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`. Note, the helm chart for the release is not public until the PR has been merged, so you cannot complete this step until after the PR is closed and the 'Tagged Build' trigger (trigger ID: 083adc1a-fcac-4033-bc38-b9f6eadcb75d) has completed, which publishes the helm chart.
|
||||
|
||||
## Finalize
|
||||
|
||||
- [ ] Make sure your release draft reflects all steps up to this point, and is saved (so contributors can review it).
|
||||
- [ ] 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.
|
||||
- [ ] Publish the [Release](om-release) on Open Match [Blog](https://open-match.dev/site/blog/).
|
||||
|
||||
## Announce
|
||||
|
||||
- [ ] Send an email to the [mailing list](https://groups.google.com/forum/#!newtopic/open-match-discuss) with the release details (copy-paste the release blog post)
|
||||
- [ ] Send a chat on the [Slack channel](https://open-match.slack.com/). "Open Match {version} has been released! Check it out at {release url}."
|
||||
|
||||
[makefile-version]: https://github.com/googleforgames/open-match/blob/main/Makefile#L53
|
||||
[om-chart-yaml-version]: https://github.com/googleforgames/open-match/blob/main/install/helm/open-match/Chart.yaml#L16
|
||||
[om-values-yaml-version]: https://github.com/googleforgames/open-match/blob/main/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/main/README.md#deploy-to-kubernetes
|
16
.github/pull_request_template.md
vendored
Normal file
16
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- 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**:
|
||||
|
||||
|
67
.gitignore
vendored
67
.gitignore
vendored
@ -1,7 +1,22 @@
|
||||
# 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
|
||||
|
||||
@ -24,9 +39,6 @@
|
||||
*.cities
|
||||
populations
|
||||
|
||||
# local config files
|
||||
*.json
|
||||
|
||||
# Discarded code snippets
|
||||
build.sh
|
||||
*-fast.yaml
|
||||
@ -34,6 +46,8 @@ detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
@ -64,6 +78,53 @@ bld/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
|
||||
# Goland
|
||||
.idea/
|
||||
|
||||
# Nodejs files placed when building Hugo, ok to allow if we actually start using Nodejs.
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
site/resources/_gen/
|
||||
|
||||
# Node Modules
|
||||
node_modules/
|
||||
|
||||
# Install YAML files
|
||||
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
|
||||
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/
|
||||
|
226
.golangci.yaml
Normal file
226
.golangci.yaml
Normal file
@ -0,0 +1,226 @@
|
||||
# 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
|
||||
- typecheck
|
||||
|
||||
#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
|
53
CHANGELOG.md
53
CHANGELOG.md
@ -1,53 +0,0 @@
|
||||
# Release history
|
||||
|
||||
## 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.
|
@ -1,7 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/internal
|
||||
RUN go get -d -v ...
|
27
Dockerfile.base-build
Normal file
27
Dockerfile.base-build
Normal file
@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
FROM golang:1.21.0
|
||||
|
||||
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 ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
75
Dockerfile.ci
Normal file
75
Dockerfile.ci
Normal file
@ -0,0 +1,75 @@
|
||||
# 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 debian:bookworm
|
||||
|
||||
# set env vars
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV OPEN_MATCH_CI_MODE=1
|
||||
ENV KUBECONFIG=$HOME/.kube/config
|
||||
ENV GOPATH=/go
|
||||
ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y -qq --no-install-recommends \
|
||||
apt-utils \
|
||||
git \
|
||||
make \
|
||||
python3 \
|
||||
virtualenv \
|
||||
curl \
|
||||
sudo \
|
||||
unzip \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
software-properties-common \
|
||||
gnupg2 \
|
||||
libc6 \
|
||||
build-essential
|
||||
RUN mkdir -p /usr/share/keyrings/
|
||||
|
||||
# Docker
|
||||
RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" | tee -a /etc/apt/sources.list.d/docker.list \
|
||||
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \
|
||||
&& apt-get update -y \
|
||||
&& apt-get install -y -qq \
|
||||
docker-ce \
|
||||
docker-ce-cli \
|
||||
containerd.io
|
||||
|
||||
# Cloud SDK
|
||||
RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk 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 --keyring /usr/share/keyrings/cloud.google.gpg add - \
|
||||
&& apt-get update -y \
|
||||
&& apt-get install -y -qq \
|
||||
google-cloud-cli \
|
||||
google-cloud-sdk-gke-gcloud-auth-plugin
|
||||
|
||||
# Install Golang
|
||||
RUN mkdir -p /toolchain/golang
|
||||
WORKDIR /toolchain/golang
|
||||
RUN rm -rf /usr/local/go/
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
# reference: https://github.com/docker-library/golang/blob/master/1.20/bookworm/Dockerfile
|
||||
RUN curl -L https://golang.org/dl/go1.21.0.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" \
|
||||
&& chmod -R 777 "$GOPATH"
|
||||
|
||||
# Prepare toolchain, workspace, homedir
|
||||
RUN mkdir -p \
|
||||
/toolchain \
|
||||
/workspace \
|
||||
$HOME/.kube/
|
||||
WORKDIR /workspace
|
62
Dockerfile.cmd
Normal file
62
Dockerfile.cmd
Normal file
@ -0,0 +1,62 @@
|
||||
# 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 --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
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/"
|
@ -1,21 +0,0 @@
|
||||
FROM php:7.2-cli
|
||||
|
||||
RUN apt-get update && apt-get install -y -q zip unzip zlib1g-dev && apt-get clean
|
||||
|
||||
RUN cd /usr/local/bin && curl -sS https://getcomposer.org/installer | php
|
||||
RUN cd /usr/local/bin && mv composer.phar composer
|
||||
|
||||
RUN pecl install grpc
|
||||
RUN echo "extension=grpc.so" > /usr/local/etc/php/conf.d/30-grpc.ini
|
||||
|
||||
RUN pecl install protobuf
|
||||
RUN echo "extension=protobuf.so" > /usr/local/etc/php/conf.d/30-protobuf.ini
|
||||
|
||||
WORKDIR /usr/src/open-match
|
||||
COPY examples/functions/php/mmlogic-simple examples/functions/php/mmlogic-simple
|
||||
COPY config config
|
||||
WORKDIR /usr/src/open-match/examples/functions/php/mmlogic-simple
|
||||
|
||||
RUN composer install
|
||||
|
||||
CMD [ "php", "./harness.php" ]
|
@ -1,9 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM python:3.5.3 as builder
|
||||
WORKDIR /usr/src/open-match
|
||||
COPY examples/functions/python3/mmlogic-simple examples/functions/python3/mmlogic-simple
|
||||
COPY config config
|
||||
WORKDIR /usr/src/open-match/examples/functions/python3/mmlogic-simple
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
CMD ["python", "./harness.py"]
|
278
README.md
278
README.md
@ -1,271 +1,39 @@
|
||||
# Open Match
|
||||

|
||||
|
||||
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 (run it anywhere Kubernetes runs), extensible (match logic can be customized to work for any game), and scalable.
|
||||
[](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)
|
||||
|
||||
Matchmaking is a complicated process, and when large player populations are involved, many popular 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 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.
|
||||
|
||||
["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)
|
||||
Please visit [Open Match website](https://open-match.dev/site/docs/) for user
|
||||
documentation, demo instructions etc.
|
||||
|
||||
This project attempts to solve the networking and plumbing problems, so game developers can focus on the logic to match players into great games.
|
||||
## Contributing to Open Match
|
||||
|
||||
## 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](#get-involved) 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.**
|
||||
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.
|
||||
|
||||
## Version
|
||||
[The current stable version in master is 0.3.1 (alpha)](https://github.com/GoogleCloudPlatform/open-match/releases/tag/v0.3.1-alpha). At this time only bugfixes and doc update pull requests will be considered.
|
||||
Version 0.4.0 is in active development; please target code changes to the 040wip branch.
|
||||
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.
|
||||
|
||||
# Core Concepts
|
||||
## Support
|
||||
|
||||
[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.
|
||||
* **MMFOrc** — Matchmaker function orchestrator. This Open Match core component is in charge of kicking off custom matchmaking functions (MMFs) and evaluator processes.
|
||||
* **MMF** — Matchmaking function. This is the customizable matchmaking logic.
|
||||
* **MMLogic API** — An API that provides MMF SDK functionality. It is optional - you can also do all the state storage read and write operations yourself if you have a good reason to do so.
|
||||
* **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.9.
|
||||
* [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.10.9.
|
||||
|
||||
## Components
|
||||
|
||||
Open Match is a set of processes designed to run on Kubernetes. It contains these **core** components:
|
||||
|
||||
1. Frontend API
|
||||
1. Backend API
|
||||
1. Matchmaker Function Orchestrator (MMFOrc) (may be deprecated in future versions)
|
||||
|
||||
It includes these **optional** (but recommended) components:
|
||||
1. Matchmaking Logic (MMLogic) API
|
||||
|
||||
It also explicitly depends on these two **customizable** components.
|
||||
|
||||
1. Matchmaking "Function" (MMF)
|
||||
1. Evaluator (may be optional in future versions)
|
||||
|
||||
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 Function Orchestrator (MMFOrc)
|
||||
|
||||
The MMFOrc kicks off your custom matchmaking function (MMF) for every unique profile submitted to the Backend API in a match object. It also runs the Evaluator to resolve conflicts in case more than one of your profiles matched the same players.
|
||||
|
||||
The MMFOrc exists to orchestrate/schedule your **custom components**, running them as often as required to meet the demands of your game. MMFOrc runs in an endless loop, submitting MMFs and Evaluator jobs to Kubernetes.
|
||||
|
||||
### 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).
|
||||
|
||||
The Evaluator is a component run by the Matchmaker Function Orchestrator (MMFOrc) after the matchmaker functions have been run, and some proposed results are available. The Evaluator 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 run by the Matchmaker Function Orchestrator (MMFOrc) — once per profile it sees in state storage. The MMF is run as a Job in Kubernetes, and has full access to read and write from state storage. At a high level, the encouraged pattern is to write a MMF in whatever language you are comfortable in that can do the following things:
|
||||
|
||||
- [x] Be packaged in a (Linux) Docker container.
|
||||
- [x] Read/write from the Open Match state storage — Open Match ships with Redis as the default state storage.
|
||||
- [x] Read a profile you wrote to state storage using the Backend API.
|
||||
- [x] Select from the player data you wrote to state storage using the Frontend API. It must respect all the ignore lists defined in the matchmaker config.
|
||||
- [ ] Run your custom logic to try to find a match.
|
||||
- [x] Write the match object it creates to state storage at a specified key.
|
||||
- [x] Remove the players it selected from consideration by other MMFs by adding them to the appropriate ignore list.
|
||||
- [x] Notify the MMFOrc of completion.
|
||||
- [x] (Optional, but recommended) Export stats for metrics collection.
|
||||
|
||||
**Open Match offers [matchmaking logic API](#matchmaking-logic-mmlogic-api) calls for handling the checked items, as long as you are able to format your input and output in the data schema Open Match expects (defined in the [protobuf messages](api/protobuf-spec/messages.proto)).** You can to do this work yourself if you don't want to or can't use the data schema Open Match is looking for. However, the data formats expected by Open Match are pretty generalized and will work with most common matchmaking scenarios and game types. If you have questions about how to fit your data into the formats specified, feel free to ask us in the [Slack or mailing group](#get-involved).
|
||||
|
||||
Example MMFs are provided in these languages:
|
||||
- [C#](examples/functions/csharp/simple) (doesn't use the MMLogic API)
|
||||
- [Python3](examples/functions/python3/mmlogic-simple) (MMLogic API enabled)
|
||||
- [PHP](examples/functions/php/mmlogic-simple) (MMLogic API enabled)
|
||||
- [golang](examples/functions/golang/manual-simple) (doesn't use the MMLogic API)
|
||||
|
||||
## Open Source Software integrations
|
||||
|
||||
### Structured logging
|
||||
|
||||
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 for metrics
|
||||
|
||||
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.
|
||||
|
||||
### Redis setup
|
||||
|
||||
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.
|
||||
|
||||
## Additional examples
|
||||
|
||||
**Note:** These examples will be expanded on in future releases.
|
||||
|
||||
The following examples of how to call the APIs are provided in the repository. Both have a `Dockerfile` and `cloudbuild.yaml` files in their respective directories:
|
||||
|
||||
* `test/cmd/frontendclient/main.go` acts as a client to the the Frontend API, putting a player into the queue with simulated latencies from major metropolitan cities and a couple of other matchmaking attributes. It then waits for you to manually put a value in Redis to simulate a server connection string being written using the backend API 'CreateAssignments' call, and displays that value on stdout for you to verify.
|
||||
* `examples/backendclient/main.go` calls the Backend API and passes in the profile found in `backendstub/profiles/testprofile.json` to the `ListMatches` API endpoint, then continually prints the results until you exit, or there are insufficient players to make a match based on the profile..
|
||||
|
||||
## Usage
|
||||
|
||||
Documentation and usage guides on how to set up and customize Open Match.
|
||||
|
||||
### Precompiled container images
|
||||
|
||||
Once we reach a 1.0 release, we plan to produce publicly available (Linux) Docker container images of major releases in a public image registry. Until then, refer to the 'Compiling from source' section below.
|
||||
|
||||
### Compiling from source
|
||||
|
||||
All components of Open Match produce (Linux) Docker container images as artifacts, and there are included `Dockerfile`s for each. [Google Cloud Platform Cloud Build](https://cloud.google.com/cloud-build/docs/) users will also find `cloudbuild.yaml` files for each component in the corresponding `cmd/<COMPONENT>` directories.
|
||||
|
||||
All the core components for Open Match are written in Golang and use the [Dockerfile multistage builder pattern](https://docs.docker.com/develop/develop-images/multistage-build/). This pattern uses intermediate Docker containers as a Golang build environment while producing lightweight, minimized container images as final build artifacts. When the project is ready for production, we will modify the `Dockerfile`s to uncomment the last build stage. Although this pattern is great for production container images, it removes most of the utilities required to troubleshoot issues during development.
|
||||
|
||||
## Configuration
|
||||
Currently, each component reads a local config file `matchmaker_config.json`, and all components assume they have the same configuration. To this end, there is a single centralized config file located in the `<REPO_ROOT>/config/` which is symlinked to each component's subdirectory for convenience when building locally. When `docker build`ing the component container images, the Dockerfile copies the centralized config file into the component directory.
|
||||
|
||||
We plan to replace this with a Kubernetes-managed config with dynamic reloading, please join the discussion in [Issue #42](issues/42).
|
||||
|
||||
### Guides
|
||||
* [Production guide](./docs/production.md) Lots of best practices to be written here before 1.0 release, right now it's a scattered collection of notes. **WIP**
|
||||
* [Development guide](./docs/development.md)
|
||||
|
||||
### Reference
|
||||
* [FAQ](./docs/faq.md)
|
||||
|
||||
## Get involved
|
||||
|
||||
* [Slack channel](https://open-match.slack.com/)
|
||||
* [Signup link](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU)
|
||||
* [Slack Channel](https://open-match.slack.com/) ([Signup](https://join.slack.com/t/open-match/shared_invite/zt-5k57lph3-Oe0WdatzL32xv6tPG3PfzQ))
|
||||
* [File an Issue](https://github.com/googleforgames/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)
|
||||
|
||||
## Development and Contribution
|
||||
|
||||
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.
|
||||
|
||||
The [Release Process](docs/governance/release_process.md) documentation displays the project's upcoming release calendar and release process. (NYI)
|
||||
|
||||
Open Match is in active development - we would love your help in shaping its future!
|
||||
|
||||
## This all sounds great, but can you explain Docker and/or Kubernetes to me?
|
||||
|
||||
### 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)
|
||||
|
||||
## Licence
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
|
||||
# Planned improvements
|
||||
See the [provisional roadmap](docs/roadmap.md) for more information on upcoming releases.
|
||||
|
||||
## Documentation
|
||||
- [ ] “Writing your first matchmaker” getting started guide will be included in an upcoming version.
|
||||
- [ ] Documentation for using the example customizable components and the `backendstub` and `frontendstub` applications to do an end-to-end (e2e) test will be written. This all works now, but needs to be written up.
|
||||
- [ ] Documentation on release process and release calendar.
|
||||
|
||||
## State storage
|
||||
- [X] All state storage operations should be isolated from core components into the `statestorage/` modules. This is necessary precursor work to enabling Open Match state storage to use software other than Redis.
|
||||
- [X] [The Redis deployment should have an example HA configuration](https://github.com/GoogleCloudPlatform/open-match/issues/41)
|
||||
- [X] Redis watch should be unified to watch a hash and stream updates. The code for this is written and validated but not committed yet.
|
||||
- [ ] We don't want to support two redis watcher code paths, but we will until golang protobuf reflection is a bit more usable. [Design doc](https://docs.google.com/document/d/19kfhro7-CnBdFqFk7l4_HmwaH2JT_Rhw5-2FLWLEGGk/edit#heading=h.q3iwtwhfujjx), [github issue](https://github.com/golang/protobuf/issues/364)
|
||||
- [X] Player/Group records generated when a client enters the matchmaking pool need to be removed after a certain amount of time with no activity. When using Redis, this will be implemented as a expiration on the player record.
|
||||
|
||||
## Instrumentation / Metrics / Analytics
|
||||
- [ ] Instrumentation of MMFs is in the planning stages. Since MMFs are by design meant to be completely customizable (to the point of allowing any process that can be packaged in a Docker container), metrics/stats will need to have an expected format and formalized outgoing pathway. Currently the thought is that it might be that the metrics should be written to a particular key in statestorage in a format compatible with opencensus, and will be collected, aggreggated, and exported to Prometheus using another process.
|
||||
- [ ] [OpenCensus tracing](https://opencensus.io/core-concepts/tracing/) will be implemented in an upcoming version. This is likely going to require knative.
|
||||
- [X] Read logrus logging configuration from matchmaker_config.json.
|
||||
|
||||
## Security
|
||||
- [ ] The Kubernetes service account used by the MMFOrc should be updated to have min required permissions. [Issue 52](issues/52)
|
||||
|
||||
## Kubernetes
|
||||
- [ ] Autoscaling isn't turned on for the Frontend or Backend API Kubernetes deployments by default.
|
||||
- [ ] A [Helm](https://helm.sh/) chart to stand up Open Match may be provided in an upcoming version. For now just use the [installation YAMLs](./install/yaml).
|
||||
- [ ] A knative-based implementation of MMFs is in the planning stages.
|
||||
|
||||
## CI / CD / Build
|
||||
- [ ] We plan to host 'official' docker images for all release versions of the core components in publicly available docker registries soon. This is tracked in [Issue #45](issues/45) and is blocked by [Issue 42](issues/42).
|
||||
- [ ] CI/CD for this repo and the associated status tags are planned.
|
||||
- [ ] Golang unit tests will be shipped in an upcoming version.
|
||||
- [ ] A full load-testing and e2e testing suite will be included in an upcoming version.
|
||||
|
||||
## Will not Implement
|
||||
- [X] Defining multiple images inside a profile for the purposes of experimentation adds another layer of complexity into profiles that can instead be handled outside of open match with custom match functions in collaboration with a director (thing that calls backend to schedule matchmaking)
|
||||
|
||||
### Special Thanks
|
||||
- Thanks to https://jbt.github.io/markdown-editor/ for help in marking this document down.
|
||||
|
@ -1,15 +1,15 @@
|
||||
# Open Match APIs
|
||||
# Open Match API
|
||||
|
||||
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.
|
||||
Open Match API is exposed via [gRPC](https://grpc.io/) and HTTP REST with [Swagger](https://swagger.io/tools/swagger-codegen/).
|
||||
|
||||
* [Protobuf .proto files for all APIs](./protobuf-spec/)
|
||||
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.
|
||||
|
||||
These proto files are copied to the container image during `docker build` for the Open Match core components. The `Dockerfiles` handle the compilation for you transparently, and copy the resulting `SPEC.pb.go` files to the appropriate place in your final container image.
|
||||
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
|
||||
|
||||
Manual gRPC compilation commmand, from the directory containing the proto:
|
||||
```protoc -I . ./<filename>.proto --go_out=plugins=grpc:.```
|
||||
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.
|
169
api/backend.proto
Normal file
169
api/backend.proto
Normal file
@ -0,0 +1,169 @@
|
||||
// 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-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.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: "*"
|
||||
};
|
||||
}
|
||||
}
|
657
api/backend.swagger.json
Normal file
657
api/backend.swagger.json
Normal file
@ -0,0 +1,657 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "BackendService"
|
||||
}
|
||||
],
|
||||
"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": "BackendService_FetchMatches",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchFetchMatchesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchFetchMatchesResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "BackendService_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"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "BackendService_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"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "BackendService_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"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseAllTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"AssignmentFailureCause": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"UNKNOWN",
|
||||
"TICKET_NOT_FOUND"
|
||||
],
|
||||
"default": "UNKNOWN"
|
||||
},
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"openmatchAssignTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$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": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"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\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \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."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \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": {
|
||||
"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. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
}
|
||||
},
|
||||
"additionalProperties": {},
|
||||
"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 // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\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 := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(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\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 }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
80
api/evaluator.proto
Normal file
80
api/evaluator.proto
Normal file
@ -0,0 +1,80 @@
|
||||
// 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-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.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/internal/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: "*"
|
||||
};
|
||||
}
|
||||
}
|
302
api/evaluator.swagger.json
Normal file
302
api/evaluator.swagger.json
Normal file
@ -0,0 +1,302 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Evaluator"
|
||||
}
|
||||
],
|
||||
"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": "Evaluator_Evaluate",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchEvaluateResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchEvaluateResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"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\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \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."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
}
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \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": {
|
||||
"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. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
}
|
||||
},
|
||||
"additionalProperties": {},
|
||||
"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 // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\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 := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(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\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 }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
24
api/extensions.proto
Normal file
24
api/extensions.proto
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.
|
||||
|
||||
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;
|
||||
}
|
222
api/frontend.proto
Normal file
222
api/frontend.proto
Normal file
@ -0,0 +1,222 @@
|
||||
// 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-openapiv2/options/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.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/internal/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;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message AcknowledgeBackfillRequest {
|
||||
// An existing ID of Backfill to acknowledge.
|
||||
string backfill_id = 1;
|
||||
|
||||
// An updated Assignment of the requested Backfill.
|
||||
Assignment assignment = 2;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message AcknowledgeBackfillResponse {
|
||||
// The Backfill that was acknowledged.
|
||||
Backfill backfill = 1;
|
||||
|
||||
// All of the Tickets that were successfully assigned
|
||||
repeated Ticket tickets = 2;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message CreateBackfillRequest {
|
||||
// An empty Backfill object.
|
||||
Backfill backfill = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message DeleteBackfillRequest {
|
||||
// An existing ID of Backfill to delete.
|
||||
string backfill_id = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message GetBackfillRequest {
|
||||
// An existing ID of Backfill to retrieve.
|
||||
string backfill_id = 1;
|
||||
}
|
||||
|
||||
// UpdateBackfillRequest - update searchFields, extensions and set assignment.
|
||||
//
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message UpdateBackfillRequest {
|
||||
// A Backfill object with ID set and fields to update.
|
||||
Backfill backfill = 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"
|
||||
};
|
||||
}
|
||||
|
||||
// AcknowledgeBackfill is used to notify OpenMatch about GameServer connection info
|
||||
// This triggers an assignment process.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc AcknowledgeBackfill(AcknowledgeBackfillRequest) returns (AcknowledgeBackfillResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/backfills/{backfill_id}/acknowledge"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// CreateBackfill creates a new Backfill object.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc CreateBackfill(CreateBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/backfills"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteBackfill receives a backfill ID and deletes its resource.
|
||||
// Any tickets waiting for this backfill will be returned to the active pool, no longer pending.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc DeleteBackfill(DeleteBackfillRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/frontendservice/backfills/{backfill_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// GetBackfill returns a backfill object by its ID.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc GetBackfill(GetBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/backfills/{backfill_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// UpdateBackfill updates search_fields and extensions for the backfill with the provided id.
|
||||
// Any tickets waiting for this backfill will be returned to the active pool, no longer pending.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc UpdateBackfill(UpdateBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
patch: "/v1/frontendservice/backfills"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
625
api/frontend.swagger.json
Normal file
625
api/frontend.swagger.json
Normal file
@ -0,0 +1,625 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "FrontendService"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/frontendservice/backfills": {
|
||||
"post": {
|
||||
"summary": "CreateBackfill creates a new Backfill object.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_CreateBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal.",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchCreateBackfillRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "UpdateBackfill updates search_fields and extensions for the backfill with the provided id.\nAny tickets waiting for this backfill will be returned to the active pool, no longer pending.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_UpdateBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"description": "UpdateBackfillRequest - update searchFields, extensions and set assignment.\n\nBETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal.",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchUpdateBackfillRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/backfills/{backfill_id}": {
|
||||
"get": {
|
||||
"summary": "GetBackfill returns a backfill object by its ID.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_GetBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to retrieve.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "DeleteBackfill receives a backfill ID and deletes its resource.\nAny tickets waiting for this backfill will be returned to the active pool, no longer pending.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_DeleteBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to delete.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/backfills/{backfill_id}/acknowledge": {
|
||||
"post": {
|
||||
"summary": "AcknowledgeBackfill is used to notify OpenMatch about GameServer connection info\nThis triggers an assignment process.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_AcknowledgeBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchAcknowledgeBackfillResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to acknowledge.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An updated Assignment of the requested Backfill."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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": "FrontendService_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"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "FrontendService_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"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "FrontendService_DeleteTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "FrontendService_WatchAssignments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchWatchAssignmentsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchWatchAssignmentsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket to get updates on.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAcknowledgeBackfillResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "The Backfill that was acknowledged."
|
||||
},
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"title": "All of the Tickets that were successfully assigned"
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"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\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \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."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"openmatchCreateBackfillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "An empty Backfill object."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \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."
|
||||
},
|
||||
"openmatchUpdateBackfillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "A Backfill object with ID set and fields to update."
|
||||
}
|
||||
},
|
||||
"description": "UpdateBackfillRequest - update searchFields, extensions and set assignment.\n\nBETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"openmatchWatchAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An updated Assignment of the requested Ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@type": {
|
||||
"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. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
}
|
||||
},
|
||||
"additionalProperties": {},
|
||||
"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 // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\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 := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(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\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 }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
81
api/matchfunction.proto
Normal file
81
api/matchfunction.proto
Normal file
@ -0,0 +1,81 @@
|
||||
// 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-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.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 logic 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: "*"
|
||||
};
|
||||
}
|
||||
}
|
425
api/matchfunction.swagger.json
Normal file
425
api/matchfunction.swagger.json
Normal file
@ -0,0 +1,425 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "MatchFunction"
|
||||
}
|
||||
],
|
||||
"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,\nruns matchmaking logic against them, then constructs and streams back\nmatch candidates to the Backend service.",
|
||||
"operationId": "MatchFunction_Run",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchRunResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchRunResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchRunRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MatchFunction"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"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\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \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."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \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": {
|
||||
"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. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
}
|
||||
},
|
||||
"additionalProperties": {},
|
||||
"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 // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\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 := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(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\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 }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
274
api/messages.proto
Normal file
274
api/messages.proto
Normal file
@ -0,0 +1,274 @@
|
||||
// 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;
|
||||
|
||||
// Customized information not inspected by Open Match, to be kept persistent
|
||||
// throughout the life-cycle of a ticket.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> persistent_field = 6;
|
||||
|
||||
// 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 = 7;
|
||||
|
||||
// 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;
|
||||
|
||||
enum Exclude {
|
||||
// No bounds should be excluded when evaluating the filter, i.e.: MIN <= x <= MAX
|
||||
NONE = 0;
|
||||
|
||||
// Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN < x <= MAX
|
||||
MIN = 1;
|
||||
|
||||
// Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN <= x < MAX
|
||||
MAX = 2;
|
||||
|
||||
// Both bounds should be excluded when evaluating the filter, i.e.: MIN < x < MAX
|
||||
BOTH = 3;
|
||||
}
|
||||
|
||||
// Defines the bounds to apply when filtering tickets by their search_fields.double_args value.
|
||||
// BETA FEATURE WARNING: This field and the associated values are
|
||||
// not finalized and still subject to possible change or removal.
|
||||
Exclude exclude = 4;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Backfill request which contains additional information to the match
|
||||
// and contains an association to a GameServer.
|
||||
// BETA FEATURE WARNING: This field is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
Backfill backfill = 8;
|
||||
|
||||
// AllocateGameServer signalise Director that Backfill is new and it should
|
||||
// allocate a GameServer, this Backfill would be assigned.
|
||||
// BETA FEATURE WARNING: This field is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
bool allocate_gameserver = 9;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 5, 6;
|
||||
}
|
||||
|
||||
// Represents a backfill entity which is used to fill partially full matches.
|
||||
//
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
message Backfill {
|
||||
// Id represents an auto-generated Id issued by Open Match.
|
||||
string id = 1;
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
SearchFields search_fields = 2;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by
|
||||
// the Match Function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 3;
|
||||
|
||||
// Customized information not inspected by Open Match, to be kept persistent
|
||||
// throughout the life-cycle of a backfill.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> persistent_field = 4;
|
||||
|
||||
// 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 = 5;
|
||||
|
||||
// Generation gets incremented on GameServers update operations.
|
||||
// Prevents the MMF from overriding a newer version from the game server.
|
||||
// Do NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs.
|
||||
int64 generation = 6;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
** 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.
|
@ -1,56 +0,0 @@
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
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(messages.MatchObject) returns (messages.MatchObject) {}
|
||||
// Continually run MMF and stream MatchObjects that fit this profile until
|
||||
// the backend client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
rpc ListMatches(messages.MatchObject) returns (stream messages.MatchObject) {}
|
||||
|
||||
// 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(messages.MatchObject) returns (messages.Result) {}
|
||||
|
||||
// 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(messages.Assignments) returns (messages.Result) {}
|
||||
// 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(messages.Roster) returns (messages.Result) {}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
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(messages.Player) returns (messages.Result) {}
|
||||
|
||||
// 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(messages.Player) returns (messages.Result) {}
|
||||
|
||||
// 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(messages.Player) returns (stream messages.Player) {}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
syntax = 'proto3';
|
||||
package messages;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/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.
|
||||
}
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
message Result{
|
||||
bool success = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
// IlInput is an empty message reserved for future use.
|
||||
message IlInput{
|
||||
}
|
||||
|
||||
message Assignments{
|
||||
repeated Roster rosters = 1;
|
||||
string assignment = 10;
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
// 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(messages.MatchObject) returns (messages.MatchObject) {}
|
||||
|
||||
// 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(messages.MatchObject) returns (messages.Result) {}
|
||||
|
||||
// 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(messages.PlayerPool) returns (stream messages.PlayerPool) {}
|
||||
|
||||
// Ignore List functions
|
||||
//
|
||||
// IlInput is an empty message reserved for future use.
|
||||
rpc GetAllIgnoredPlayers(messages.IlInput) returns (messages.Roster) {}
|
||||
// ListIgnoredPlayers retrieves players from the ignore list specified in the
|
||||
// config file under 'ignoreLists.proposed.name'.
|
||||
rpc ListIgnoredPlayers(messages.IlInput) returns (messages.Roster) {}
|
||||
|
||||
// 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) {}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. mmlogic.proto
|
||||
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. messages.proto
|
||||
cp *pb2* $OM/examples/functions/python3/simple/.
|
@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Script to compile golang versions of the OM proto files
|
||||
#
|
||||
# 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.
|
||||
|
||||
cd $GOPATH/src
|
||||
protoc \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/backend.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/frontend.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/mmlogic.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/messages.proto \
|
||||
-I ${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/ \
|
||||
--go_out=plugins=grpc:$GOPATH/src
|
||||
cd -
|
125
api/query.proto
Normal file
125
api/query.proto
Normal file
@ -0,0 +1,125 @@
|
||||
// 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-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.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/internal/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;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request messages are not finalized and
|
||||
// still subject to possible change or removal.
|
||||
message QueryBackfillsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request messages are not finalized and
|
||||
// still subject to possible change or removal.
|
||||
message QueryBackfillsResponse {
|
||||
// Backfills that meet all the filtering criteria requested by the pool.
|
||||
repeated Backfill backfills = 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: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// QueryBackfills gets a list of Backfills.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc QueryBackfills(QueryBackfillsRequest) returns (stream QueryBackfillsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/backfills:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
507
api/query.swagger.json
Normal file
507
api/query.swagger.json
Normal file
@ -0,0 +1,507 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "QueryService"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/queryservice/backfills:query": {
|
||||
"post": {
|
||||
"summary": "QueryBackfills gets a list of Backfills.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "QueryService_QueryBackfills",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryBackfillsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryBackfillsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"description": "BETA FEATURE WARNING: This Request messages are not finalized and \nstill subject to possible change or removal.",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryBackfillsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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": "QueryService_QueryTicketIds",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketIdsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketIdsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "QueryService_QueryTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"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\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \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."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"openmatchQueryBackfillsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request messages are not finalized and \nstill subject to possible change or removal."
|
||||
},
|
||||
"openmatchQueryBackfillsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
},
|
||||
"description": "Backfills that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request messages are not finalized and \nstill subject to possible change or removal."
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"$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."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \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": {
|
||||
"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. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
}
|
||||
},
|
||||
"additionalProperties": {},
|
||||
"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 // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\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 := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(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\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 }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
198
cloudbuild.yaml
Normal file
198
cloudbuild.yaml
Normal file
@ -0,0 +1,198 @@
|
||||
# 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.
|
||||
|
||||
################################################################################
|
||||
# Open Match Script for Google Cloud Build #
|
||||
################################################################################
|
||||
|
||||
# example command-line invocation:
|
||||
# gcloud builds submit --config=cloudbuild.yaml --substitutions=_OM_VERSION=DEV .
|
||||
|
||||
# This YAML contains all the build steps for building Open Match.
|
||||
# All PRs are verified against this script to prevent build breakages and regressions.
|
||||
|
||||
# Conventions
|
||||
# Each build step is ID'ed with "Prefix: Description".
|
||||
# The prefix portion determines what kind of step it is and it's impact.
|
||||
# Docker Image: Read-Only, outputs a docker image.
|
||||
# Lint: Read-Only, verifies correctness and formatting of a file.
|
||||
# Build: Read-Write, outputs a build artifact. Ok to run in parallel if the artifact will not collide with another one.
|
||||
# Generate: Read-Write, outputs files within /workspace that are used in other build step. Do not run these in parallel.
|
||||
# Setup: Read-Write, similar to generate but steps that run before any other step.
|
||||
|
||||
# Some useful things to know about Cloud Build.
|
||||
# When your build executes, Cloud Build copies the contents of your repository to /workspace, the default working directory for Cloud Build.
|
||||
# Learn more about working directories on the Build configuration overview page https://cloud.google.com/build/docs/build-config-file-schema
|
||||
# - Modifications that occur within /workspace are persisted between build steps.
|
||||
# - If you want to replicate the build process from this file locally, you'll need to
|
||||
# clone the open match github repo and set HEAD to the commit you're trying to build.
|
||||
# If a build step has intermediate files that need to be persisted for a future step then use volumes.
|
||||
# An example of this is the go-vol which is where the pkg/ data for go mod is stored.
|
||||
# More information here: https://cloud.google.com/cloud-build/docs/build-config#build_steps
|
||||
# A build step is basically a docker image that is tuned for Cloud Build,
|
||||
# https://github.com/GoogleCloudPlatform/cloud-builders/tree/master/go
|
||||
|
||||
steps:
|
||||
- id: 'Docker Image: open-match-build'
|
||||
name: gcr.io/kaniko-project/executor:latest
|
||||
args: [
|
||||
"--destination=gcr.io/$PROJECT_ID/open-match-build",
|
||||
"--dockerfile=Dockerfile.ci",
|
||||
"--cache=true",
|
||||
"--cache-ttl=3600h",
|
||||
]
|
||||
waitFor: ['-']
|
||||
#name: gcr.io/cloud-builders/docker
|
||||
#args: ['build', '-t', 'gcr.io/$PROJECT_ID/open-match-build', '-f', 'Dockerfile.ci', '.']
|
||||
#waitFor: ['-']
|
||||
|
||||
- id: 'Build: Clean'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'clean-third-party', 'clean-protos', 'clean-swagger-docs']
|
||||
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: Clean Go Dependencies'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'clean-deps']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Clean']
|
||||
|
||||
- id: 'Build: Initialize Toolchain'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'install-toolchain']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Setup: Clean Go Dependencies']
|
||||
|
||||
- id: 'Build: Compile Protos'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'golang-protos']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Initialize Toolchain']
|
||||
|
||||
- id: 'Setup: Download Go Dependencies'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'sync-deps']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Compile Protos']
|
||||
|
||||
- id: 'Build: Deployment Configs'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'SHORT_SHA=${SHORT_SHA}', 'update-chart-deps', 'install/yaml/']
|
||||
waitFor: ['Setup: Download Go Dependencies']
|
||||
|
||||
- id: 'Test: Terraform Configuration'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'terraform-test']
|
||||
waitFor: ['Setup: Download Go Dependencies']
|
||||
|
||||
- id: 'Build: Assets'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_CHARTS_BUCKET=${_CHARTS_BUCKET}', 'assets', '-j12']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Deployment Configs']
|
||||
|
||||
- id: 'Build: Binaries'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_CHARTS_BUCKET=${_CHARTS_BUCKET}', 'build', 'all', '-j12']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
|
||||
- id: 'Test: Services'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GOLANG_TEST_COUNT=3', 'test']
|
||||
# When debugging failing tests, enable verbose 'go test' output, run additional passes
|
||||
#args: ['make', 'GOLANG_EXTRA_TEST_FLAGS=-v', 'GOLANG_TEST_COUNT=10', 'test']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
|
||||
- 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']
|
||||
|
||||
- id: 'Lint: Format, Vet, Charts'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'lint']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
waitFor: ['Build: Assets']
|
||||
|
||||
- id: 'Test: Deploy Open Match'
|
||||
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']
|
||||
|
||||
- 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}', '_CHARTS_BUCKET=${_CHARTS_BUCKET}', 'ci-deploy-artifacts']
|
||||
waitFor: ['Lint: Format, Vet, Charts', 'Test: Deploy Open Match']
|
||||
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: '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']
|
||||
|
||||
artifacts:
|
||||
objects:
|
||||
location: '${_ARTIFACTS_BUCKET}${_OM_VERSION}'
|
||||
paths:
|
||||
- install/yaml/*.yaml
|
||||
- pkg/pb/*.pb.go
|
||||
- pkg/pb/*.pb.gw.go
|
||||
- internal/ipb/*.pb.go
|
||||
- api/*.swagger.json
|
||||
|
||||
substitutions:
|
||||
_OM_VERSION: "1.8.1"
|
||||
_GCB_POST_SUBMIT: "0"
|
||||
_GCB_LATEST_VERSION: "undefined"
|
||||
_ARTIFACTS_BUCKET: "gs://open-match-build-artifacts/"
|
||||
_LOGS_BUCKET: "gs://open-match-build-logs/"
|
||||
_CHARTS_BUCKET: "gs://open-match-chart"
|
||||
logsBucket: '${_LOGS_BUCKET}'
|
||||
options:
|
||||
sourceProvenanceHash: ['SHA256']
|
||||
machineType: 'N1_HIGHCPU_32'
|
||||
timeout: 2500s
|
@ -1,9 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-base:dev',
|
||||
'-f', 'Dockerfile.base',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-base:dev']
|
@ -1,9 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf-php-mmlogic-simple',
|
||||
'-f', 'Dockerfile.mmf_php',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf-php-mmlogic-simple']
|
@ -1,9 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf-py3-mmlogic-simple:dev',
|
||||
'-f', 'Dockerfile.mmf_py3',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf-py3-mmlogic-simple:dev']
|
25
cmd/backend/backend.go
Normal file
25
cmd/backend/backend.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM gcr.io/open-match-public-images/openmatch-base:dev as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/backendapi
|
||||
COPY . .
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
#FROM scratch
|
||||
#COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/backendapi/backendapi .
|
||||
ENTRYPOINT ["./backendapi"]
|
@ -1,447 +0,0 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in
|
||||
../../../api/protobuf-spec/backend.proto
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
backend "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/ignorelist"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/rs/xid"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
beLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "backend",
|
||||
}
|
||||
beLog = log.WithFields(beLogFields)
|
||||
)
|
||||
|
||||
// BackendAPI implements backend API Server, the server generated by compiling
|
||||
// the protobuf, by fulfilling the API Client interface.
|
||||
type BackendAPI struct {
|
||||
grpc *grpc.Server
|
||||
cfg *viper.Viper
|
||||
pool *redis.Pool
|
||||
}
|
||||
type backendAPI BackendAPI
|
||||
|
||||
// New returns an instantiated srvice
|
||||
func New(cfg *viper.Viper, pool *redis.Pool) *BackendAPI {
|
||||
s := BackendAPI{
|
||||
pool: pool,
|
||||
grpc: grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{})),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(BeLogLines, KeySeverity))
|
||||
|
||||
backend.RegisterBackendServer(s.grpc, (*backendAPI)(&s))
|
||||
beLog.Info("Successfully registered gRPC server")
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *BackendAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.backend.port"))
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"port": s.cfg.GetInt("api.backend.port"),
|
||||
}).Error("net.Listen() error")
|
||||
return err
|
||||
}
|
||||
|
||||
beLog.WithFields(log.Fields{"port": s.cfg.GetInt("api.backend.port")}).Info("TCP net listener initialized")
|
||||
|
||||
go func() {
|
||||
err := s.grpc.Serve(ln)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{"error": err.Error()}).Error("gRPC serve() error")
|
||||
}
|
||||
beLog.Info("serving gRPC endpoints")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateMatch is this service's implementation of the CreateMatch gRPC method
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
func (s *backendAPI) CreateMatch(c context.Context, profile *backend.MatchObject) (*backend.MatchObject, error) {
|
||||
|
||||
// Get a cancel-able context
|
||||
ctx, cancel := context.WithCancel(c)
|
||||
defer cancel()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreateMatch"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Generate a request to fill the profile. Make a unique request ID.
|
||||
moID := xid.New().String()
|
||||
requestKey := moID + "." + profile.Id
|
||||
|
||||
/*
|
||||
// Debugging logs
|
||||
beLog.Info("Pools nil? ", (profile.Pools == nil))
|
||||
beLog.Info("Pools empty? ", (len(profile.Pools) == 0))
|
||||
beLog.Info("Rosters nil? ", (profile.Rosters == nil))
|
||||
beLog.Info("Rosters empty? ", (len(profile.Rosters) == 0))
|
||||
beLog.Info("config set for json.pools?", s.cfg.IsSet("jsonkeys.pools"))
|
||||
beLog.Info("contents key?", s.cfg.GetString("jsonkeys.pools"))
|
||||
beLog.Info("contents exist?", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).Exists())
|
||||
*/
|
||||
|
||||
// Case where no protobuf pools was passed; check if there's a JSON version in the properties.
|
||||
// This is for backwards compatibility, it is recommended you populate the protobuf's
|
||||
// 'pools' field directly and pass it to CreateMatch/ListMatches
|
||||
if profile.Pools == nil && s.cfg.IsSet("jsonkeys.pools") &&
|
||||
gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).Exists() {
|
||||
poolsJSON := fmt.Sprintf("{\"pools\": %v}", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).String())
|
||||
ppLog := beLog.WithFields(log.Fields{"jsonkey": s.cfg.GetString("jsonkeys.pools")})
|
||||
ppLog.Info("poolsJSON: ", poolsJSON)
|
||||
|
||||
ppools := &backend.MatchObject{}
|
||||
err := jsonpb.UnmarshalString(poolsJSON, ppools)
|
||||
if err != nil {
|
||||
ppLog.Error("failed to parse JSON to protobuf pools")
|
||||
} else {
|
||||
profile.Pools = ppools.Pools
|
||||
ppLog.Info("parsed JSON to protobuf pools")
|
||||
}
|
||||
}
|
||||
|
||||
// Case where no protobuf roster was passed; check if there's a JSON version in the properties.
|
||||
// This is for backwards compatibility, it is recommended you populate the
|
||||
// protobuf's 'rosters' field directly and pass it to CreateMatch/ListMatches
|
||||
if profile.Rosters == nil && s.cfg.IsSet("jsonkeys.rosters") &&
|
||||
gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.rosters")).Exists() {
|
||||
rostersJSON := fmt.Sprintf("{\"rosters\": %v}", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.rosters")).String())
|
||||
rLog := beLog.WithFields(log.Fields{"jsonkey": s.cfg.GetString("jsonkeys.rosters")})
|
||||
|
||||
prosters := &backend.MatchObject{}
|
||||
err := jsonpb.UnmarshalString(rostersJSON, prosters)
|
||||
if err != nil {
|
||||
rLog.Error("failed to parse JSON to protobuf rosters")
|
||||
} else {
|
||||
profile.Rosters = prosters.Rosters
|
||||
rLog.Info("parsed JSON to protobuf rosters")
|
||||
}
|
||||
}
|
||||
|
||||
// Add fields for all subsequent logging
|
||||
beLog = beLog.WithFields(log.Fields{
|
||||
"profileID": profile.Id,
|
||||
"func": funcName,
|
||||
"matchObjectID": moID,
|
||||
"requestKey": requestKey,
|
||||
})
|
||||
beLog.Info("gRPC call executing")
|
||||
beLog.Info("profile is")
|
||||
beLog.Info(profile)
|
||||
|
||||
// Write profile to state storage
|
||||
err := redispb.MarshalToRedis(ctx, s.pool, profile, s.cfg.GetInt("redis.expirations.matchobject"))
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage failure to create match profile")
|
||||
|
||||
// Failure! Return empty match object and the error
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.MatchObject{}, err
|
||||
}
|
||||
beLog.Info("Profile written to state storage")
|
||||
|
||||
// Queue the request ID to be sent to an MMF
|
||||
_, err = redisHelpers.Update(ctx, s.pool, s.cfg.GetString("queues.profiles.name"), requestKey)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage failure to queue profile")
|
||||
|
||||
// Failure! Return empty match object and the error
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.MatchObject{}, err
|
||||
}
|
||||
beLog.Info("Profile added to processing queue")
|
||||
|
||||
// get and return matchobject, it will be written to the requestKey when the MMF has finished.
|
||||
var ok bool
|
||||
newMO := backend.MatchObject{Id: requestKey}
|
||||
watchChan := redispb.Watcher(ctx, s.pool, newMO) // Watcher() runs the appropriate Redis commands.
|
||||
errString := ("Error retrieving matchmaking results from state storage")
|
||||
timeout := time.Duration(s.cfg.GetInt("api.backend.timeout")) * time.Second
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
// TODO:Timeout: deal with the fallout. There are some edge cases here.
|
||||
// When there is a timeout, need to send a stop to the watch channel.
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return profile, errors.New(errString + ": timeout exceeded")
|
||||
|
||||
case newMO, ok = <-watchChan:
|
||||
if !ok {
|
||||
// ok is false if watchChan has been closed by redispb.Watcher()
|
||||
newMO.Error = newMO.Error + "; channel closed - was the context cancelled?"
|
||||
} else {
|
||||
// 'ok' was true, so properties should contain the results from redis.
|
||||
// Do basic error checking on the returned JSON
|
||||
if !gjson.Valid(profile.Properties) {
|
||||
newMO.Error = "retreived properties json was malformed"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test that this is the correct condition for an empty error.
|
||||
if newMO.Error != "" {
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &newMO, errors.New(newMO.Error)
|
||||
}
|
||||
|
||||
// Got results; close the channel so the Watcher() function stops querying redis.
|
||||
}
|
||||
|
||||
beLog.Info("Matchmaking results received, returning to backend client")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return &newMO, err
|
||||
}
|
||||
|
||||
// ListMatches is this service's implementation of the ListMatches gRPC method
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
// This is the streaming version of CreateMatch - continually submitting the
|
||||
// profile to be filled until the requesting service ends the connection.
|
||||
func (s *backendAPI) ListMatches(p *backend.MatchObject, matchStream backend.Backend_ListMatchesServer) error {
|
||||
|
||||
// call creatematch in infinite loop as long as the stream is open
|
||||
ctx := matchStream.Context() // https://talks.golang.org/2015/gotham-grpc.slide#30
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "ListMatches"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
}).Info("gRPC call executing. Calling CreateMatch. Looping until cancelled.")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context cancelled, probably because the client cancelled their request, time to exit.
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
}).Info("gRPC Context cancelled; client is probably finished receiving matches")
|
||||
|
||||
// TODO: need to make sure that in-flight matches don't get leaked here.
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return nil
|
||||
|
||||
default:
|
||||
// Retreive results from Redis
|
||||
requestProfile := proto.Clone(p).(*backend.MatchObject)
|
||||
/*
|
||||
beLog.Debug("new profile requested!")
|
||||
beLog.Debug(requestProfile)
|
||||
beLog.Debug(&requestProfile)
|
||||
*/
|
||||
mo, err := s.CreateMatch(ctx, requestProfile)
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{"error": err.Error()}).Error("Failure calling CreateMatch")
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
beLog.WithFields(log.Fields{"matchProperties": fmt.Sprintf("%v", mo)}).Debug("Streaming back match object")
|
||||
matchStream.Send(mo)
|
||||
|
||||
// TODO: This should be tunable, but there should be SOME sleep here, to give a requestor a window
|
||||
// to cleanly close the connection after receiving a match object when they know they don't want to
|
||||
// request any more matches.
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteMatch is this service's implementation of the DeleteMatch gRPC method
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
func (s *backendAPI) DeleteMatch(ctx context.Context, mo *backend.MatchObject) (*backend.Result, error) {
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "DeleteMatch"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
beLog.WithFields(log.Fields{
|
||||
"matchObjectID": mo.Id,
|
||||
}).Info("gRPC call executing")
|
||||
|
||||
err := redisHelpers.Delete(ctx, s.pool, mo.Id)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
beLog.WithFields(log.Fields{
|
||||
"matchObjectID": mo.Id,
|
||||
}).Info("Match Object deleted.")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return &backend.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
// CreateAssignments is this service's implementation of the CreateAssignments gRPC method
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
func (s *backendAPI) CreateAssignments(ctx context.Context, a *backend.Assignments) (*backend.Result, error) {
|
||||
|
||||
// Make a map of players and what assignments we want to send them.
|
||||
playerIDs := make([]string, 0)
|
||||
players := make(map[string]string, 0)
|
||||
for _, roster := range a.Rosters { // Loop through all rosters
|
||||
for _, player := range roster.Players { // Loop through all players in this roster
|
||||
if player.Id != "" {
|
||||
if player.Assignment == "" {
|
||||
// No player-specific assignment, so use the default one in
|
||||
// the Assignment message.
|
||||
player.Assignment = a.Assignment
|
||||
}
|
||||
players[player.Id] = player.Assignment
|
||||
beLog.Debug(fmt.Sprintf("playerid %v assignment %v", player.Id, player.Assignment))
|
||||
}
|
||||
}
|
||||
playerIDs = append(playerIDs, getPlayerIdsFromRoster(roster)...)
|
||||
}
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreateAssignments"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
beLog.WithFields(log.Fields{
|
||||
"numAssignments": len(players),
|
||||
}).Info("gRPC call executing")
|
||||
|
||||
// TODO: These two calls are done in two different transactions; could be
|
||||
// combined as an optimization but probably not particularly necessary
|
||||
// Send the players their assignments.
|
||||
err := redisHelpers.UpdateMultiFields(ctx, s.pool, players, "assignment")
|
||||
|
||||
// Move these players from the proposed list to the deindexed list.
|
||||
ignorelist.Move(ctx, s.pool, playerIDs, "proposed", "deindexed")
|
||||
|
||||
// Issue encountered
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
stats.Record(fnCtx, BeAssignmentFailures.M(int64(len(players))))
|
||||
return &backend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Success!
|
||||
beLog.WithFields(log.Fields{
|
||||
"numPlayers": len(players),
|
||||
}).Info("Assignments complete")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
stats.Record(fnCtx, BeAssignments.M(int64(len(players))))
|
||||
return &backend.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
// DeleteAssignments is this service's implementation of the DeleteAssignments gRPC method
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
func (s *backendAPI) DeleteAssignments(ctx context.Context, r *backend.Roster) (*backend.Result, error) {
|
||||
assignments := getPlayerIdsFromRoster(r)
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "DeleteAssignments"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
beLog.WithFields(log.Fields{
|
||||
"numAssignments": len(assignments),
|
||||
}).Info("gRPC call executing")
|
||||
|
||||
err := redisHelpers.DeleteMultiFields(ctx, s.pool, assignments, "assignment")
|
||||
|
||||
// Issue encountered
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
stats.Record(fnCtx, BeAssignmentDeletionFailures.M(int64(len(assignments))))
|
||||
return &backend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Success!
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
stats.Record(fnCtx, BeAssignmentDeletions.M(int64(len(assignments))))
|
||||
return &backend.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
// getPlayerIdsFromRoster returns the slice of player ID strings contained in
|
||||
// the input roster.
|
||||
func getPlayerIdsFromRoster(r *backend.Roster) []string {
|
||||
playerIDs := make([]string, 0)
|
||||
for _, p := range r.Players {
|
||||
playerIDs = append(playerIDs, p.Id)
|
||||
}
|
||||
return playerIDs
|
||||
|
||||
}
|
@ -1,178 +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
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// OpenCensus Measures. These are exported as metrics to your monitoring system
|
||||
// https://godoc.org/go.opencensus.io/stats
|
||||
//
|
||||
// When making opencensus stats, the 'name' param, with forward slashes changed
|
||||
// to underscores, is appended to the 'namespace' value passed to the
|
||||
// prometheus exporter to become the Prometheus metric name. You can also look
|
||||
// into having Prometheus rewrite your metric names on scrape.
|
||||
//
|
||||
// For example:
|
||||
// - defining the promethus export namespace "open_match" when instanciating the exporter:
|
||||
// pe, err := promethus.NewExporter(promethus.Options{Namespace: "open_match"})
|
||||
// - and naming the request counter "backend/requests_total":
|
||||
// MGrpcRequests := stats.Int64("backendapi/requests_total", ...
|
||||
// - results in the prometheus metric name:
|
||||
// open_match_backendapi_requests_total
|
||||
// - [note] when using opencensus views to aggregate the metrics into
|
||||
// distribution buckets and such, multiple metrics
|
||||
// will be generated with appended types ("<metric>_bucket",
|
||||
// "<metric>_count", "<metric>_sum", for example)
|
||||
//
|
||||
// In addition, OpenCensus stats propogated to Prometheus have the following
|
||||
// auto-populated labels pulled from kubernetes, which we should avoid to
|
||||
// prevent overloading and having to use the HonorLabels param in Prometheus.
|
||||
//
|
||||
// - Information about the k8s pod being monitored:
|
||||
// "pod" (name of the monitored k8s pod)
|
||||
// "namespace" (k8s namespace of the monitored pod)
|
||||
// - Information about how promethus is gathering the metrics:
|
||||
// "instance" (IP and port number being scraped by prometheus)
|
||||
// "job" (name of the k8s service being scraped by prometheus)
|
||||
// "endpoint" (name of the k8s port in the k8s service being scraped by prometheus)
|
||||
//
|
||||
var (
|
||||
// API instrumentation
|
||||
BeGrpcRequests = stats.Int64("backendapi/requests_total", "Number of requests to the gRPC Backend API endpoints", "1")
|
||||
BeGrpcErrors = stats.Int64("backendapi/errors_total", "Number of errors generated by the gRPC Backend API endpoints", "1")
|
||||
BeGrpcLatencySecs = stats.Float64("backendapi/latency_seconds", "Latency in seconds of the gRPC Backend API endpoints", "1")
|
||||
|
||||
// Logging instrumentation
|
||||
// There's no need to record this measurement directly if you use
|
||||
// the logrus hook provided in metrics/helper.go after instantiating the
|
||||
// logrus instance in your application code.
|
||||
// https://godoc.org/github.com/sirupsen/logrus#LevelHooks
|
||||
BeLogLines = stats.Int64("backendapi/logs_total", "Number of Backend API lines logged", "1")
|
||||
|
||||
// Failure instrumentation
|
||||
BeFailures = stats.Int64("backendapi/failures_total", "Number of Backend API failures", "1")
|
||||
|
||||
// Counting operations
|
||||
BeAssignments = stats.Int64("backendapi/assignments_total", "Number of players assigned to matches", "1")
|
||||
BeAssignmentFailures = stats.Int64("backendapi/assignment/failures_total", "Number of player match assigment failures", "1")
|
||||
BeAssignmentDeletions = stats.Int64("backendapi/assignment/deletions_total", "Number of player match assigment deletions", "1")
|
||||
BeAssignmentDeletionFailures = stats.Int64("backendapi/assignment/deletions/failures_total", "Number of player match assigment deletion failures", "1")
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyMethod is used to tag a measure with the currently running API method.
|
||||
KeyMethod, _ = tag.NewKey("method")
|
||||
// KeySeverity is used to tag a the severity of a log message.
|
||||
KeySeverity, _ = tag.NewKey("severity")
|
||||
)
|
||||
|
||||
var (
|
||||
// Latency in buckets:
|
||||
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
|
||||
latencyDistribution = view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000)
|
||||
)
|
||||
|
||||
// Package metrics provides some convience views.
|
||||
// You need to register the views for the data to actually be collected.
|
||||
// Note: The OpenCensus View 'Description' is exported to Prometheus as the HELP string.
|
||||
// Note: If you get a "Failed to export to Prometheus: inconsistent label
|
||||
// cardinality" error, chances are you forgot to set the tags specified in the
|
||||
// view for a given measure when you tried to do a stats.Record()
|
||||
var (
|
||||
BeLatencyView = &view.View{
|
||||
Name: "backend/latency",
|
||||
Measure: BeGrpcLatencySecs,
|
||||
Description: "The distribution of backend latencies",
|
||||
Aggregation: latencyDistribution,
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
BeRequestCountView = &view.View{
|
||||
Name: "backend/grpc/requests",
|
||||
Measure: BeGrpcRequests,
|
||||
Description: "The number of successful backend gRPC requests",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
BeErrorCountView = &view.View{
|
||||
Name: "backend/grpc/errors",
|
||||
Measure: BeGrpcErrors,
|
||||
Description: "The number of gRPC errors",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
BeLogCountView = &view.View{
|
||||
Name: "log_lines/total",
|
||||
Measure: BeLogLines,
|
||||
Description: "The number of lines logged",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeySeverity},
|
||||
}
|
||||
|
||||
BeFailureCountView = &view.View{
|
||||
Name: "failures",
|
||||
Measure: BeFailures,
|
||||
Description: "The number of failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
BeAssignmentCountView = &view.View{
|
||||
Name: "backend/assignments",
|
||||
Measure: BeAssignments,
|
||||
Description: "The number of successful player match assignments",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
BeAssignmentFailureCountView = &view.View{
|
||||
Name: "backend/assignments/failures",
|
||||
Measure: BeAssignmentFailures,
|
||||
Description: "The number of player match assignment failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
BeAssignmentDeletionCountView = &view.View{
|
||||
Name: "backend/assignments/deletions",
|
||||
Measure: BeAssignmentDeletions,
|
||||
Description: "The number of successful player match assignments",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
BeAssignmentDeletionFailureCountView = &view.View{
|
||||
Name: "backend/assignments/deletions/failures",
|
||||
Measure: BeAssignmentDeletionFailures,
|
||||
Description: "The number of player match assignment failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultBackendAPIViews are the default backend API OpenCensus measure views.
|
||||
var DefaultBackendAPIViews = []*view.View{
|
||||
BeLatencyView,
|
||||
BeRequestCountView,
|
||||
BeErrorCountView,
|
||||
BeLogCountView,
|
||||
BeFailureCountView,
|
||||
BeAssignmentCountView,
|
||||
BeAssignmentFailureCountView,
|
||||
BeAssignmentDeletionCountView,
|
||||
BeAssignmentDeletionFailureCountView,
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-base:dev' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-backendapi:dev',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-backendapi:dev']
|
@ -1,14 +0,0 @@
|
||||
/*
|
||||
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
|
@ -1,103 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/cmd/backendapi/apisrv"
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/logging"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
redishelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
beLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "backend",
|
||||
}
|
||||
beLog = log.WithFields(beLogFields)
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
err = errors.New("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(apisrv.BeLogLines, apisrv.KeySeverity))
|
||||
|
||||
// Viper config management initialization
|
||||
cfg, err = config.Read()
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Unable to load config file")
|
||||
}
|
||||
|
||||
// Configure open match logging defaults
|
||||
logging.ConfigureLogging(cfg)
|
||||
|
||||
// Configure OpenCensus exporter to Prometheus
|
||||
// metrics.ConfigureOpenCensusPrometheusExporter expects that every OpenCensus view you
|
||||
// want to register is in an array, so append any views you want from other
|
||||
// packages to a single array here.
|
||||
ocServerViews := apisrv.DefaultBackendAPIViews // BackendAPI OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, ocgrpc.DefaultServerViews...) // gRPC OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, config.CfgVarCountView) // config loader view.
|
||||
// Waiting on https://github.com/opencensus-integrations/redigo/pull/1
|
||||
// ocServerViews = append(ocServerViews, redis.ObservabilityMetricViews...) // redis OpenCensus views.
|
||||
beLog.WithFields(log.Fields{"viewscount": len(ocServerViews)}).Info("Loaded OpenCensus views")
|
||||
metrics.ConfigureOpenCensusPrometheusExporter(cfg, ocServerViews)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Connect to redis
|
||||
pool := redishelpers.ConnectionPool(cfg)
|
||||
defer pool.Close()
|
||||
|
||||
// Instantiate the gRPC server with the connections we've made
|
||||
beLog.Info("Attempting to start gRPC server")
|
||||
srv := apisrv.New(cfg, pool)
|
||||
|
||||
// Run the gRPC server
|
||||
err := srv.Open()
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{"error": err.Error()}).Fatal("Failed to start gRPC server")
|
||||
}
|
||||
|
||||
// Exit when we see a signal
|
||||
terminate := make(chan os.Signal, 1)
|
||||
signal.Notify(terminate, os.Interrupt)
|
||||
<-terminate
|
||||
beLog.Info("Shutting down gRPC server")
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../config/matchmaker_config.json
|
24
cmd/default-evaluator/main.go
Normal file
24
cmd/default-evaluator/main.go
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.
|
||||
|
||||
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)
|
||||
}
|
31
cmd/demo-first-match/main.go
Normal file
31
cmd/demo-first-match/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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,
|
||||
})
|
||||
}
|
25
cmd/frontend/frontend.go
Normal file
25
cmd/frontend/frontend.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM gcr.io/open-match-public-images/openmatch-base:dev as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi
|
||||
COPY . .
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
#FROM scratch
|
||||
#COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/frontendapi .
|
||||
ENTRYPOINT ["./frontendapi"]
|
@ -1,239 +0,0 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/frontend.proto.
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
frontend "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/playerindices"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
feLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "frontend",
|
||||
}
|
||||
feLog = log.WithFields(feLogFields)
|
||||
)
|
||||
|
||||
// FrontendAPI implements frontend.ApiServer, the server generated by compiling
|
||||
// the protobuf, by fulfilling the frontend.APIClient interface.
|
||||
type FrontendAPI struct {
|
||||
grpc *grpc.Server
|
||||
cfg *viper.Viper
|
||||
pool *redis.Pool
|
||||
}
|
||||
type frontendAPI FrontendAPI
|
||||
|
||||
// New returns an instantiated srvice
|
||||
func New(cfg *viper.Viper, pool *redis.Pool) *FrontendAPI {
|
||||
s := FrontendAPI{
|
||||
pool: pool,
|
||||
grpc: grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{})),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(FeLogLines, KeySeverity))
|
||||
|
||||
// Register gRPC server
|
||||
frontend.RegisterFrontendServer(s.grpc, (*frontendAPI)(&s))
|
||||
feLog.Info("Successfully registered gRPC server")
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *FrontendAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.frontend.port"))
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"port": s.cfg.GetInt("api.frontend.port"),
|
||||
}).Error("net.Listen() error")
|
||||
return err
|
||||
}
|
||||
feLog.WithFields(log.Fields{"port": s.cfg.GetInt("api.frontend.port")}).Info("TCP net listener initialized")
|
||||
|
||||
go func() {
|
||||
err := s.grpc.Serve(ln)
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{"error": err.Error()}).Error("gRPC serve() error")
|
||||
}
|
||||
feLog.Info("serving gRPC endpoints")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePlayer is this service's implementation of the CreatePlayer gRPC method defined in frontend.proto
|
||||
func (s *frontendAPI) CreatePlayer(ctx context.Context, group *frontend.Player) (*frontend.Result, error) {
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreatePlayer"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Write group
|
||||
err := redispb.MarshalToRedis(ctx, s.pool, group, s.cfg.GetInt("redis.expirations.player"))
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, FeGrpcErrors.M(1))
|
||||
return &frontend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Index group
|
||||
err = playerindices.Create(ctx, s.pool, s.cfg, *group)
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, FeGrpcErrors.M(1))
|
||||
return &frontend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Return success.
|
||||
stats.Record(fnCtx, FeGrpcRequests.M(1))
|
||||
return &frontend.Result{Success: true, Error: ""}, err
|
||||
|
||||
}
|
||||
|
||||
// DeletePlayer is this service's implementation of the DeletePlayer gRPC method defined in frontend.proto
|
||||
func (s *frontendAPI) DeletePlayer(ctx context.Context, group *frontend.Player) (*frontend.Result, error) {
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "DeletePlayer"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Deindex this player; at that point they don't show up in MMFs anymore. We can then delete
|
||||
// their actual player object from Redis later.
|
||||
err := playerindices.Delete(ctx, s.pool, s.cfg, group.Id)
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, FeGrpcErrors.M(1))
|
||||
return &frontend.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
// Kick off delete but don't wait for it to complete.
|
||||
go s.deletePlayer(group.Id)
|
||||
|
||||
stats.Record(fnCtx, FeGrpcRequests.M(1))
|
||||
return &frontend.Result{Success: true, Error: ""}, err
|
||||
|
||||
}
|
||||
|
||||
// deletePlayer is a 'lazy' player delete
|
||||
// It should always be called as a goroutine and should only be called after
|
||||
// confirmation that a player has been deindexed (and therefore MMF's can't
|
||||
// find the player to read them anyway)
|
||||
// As a final action, it also kicks off a lazy delete of the player's metadata
|
||||
func (s *frontendAPI) deletePlayer(id string) {
|
||||
|
||||
err := redisHelpers.Delete(context.Background(), s.pool, id)
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Warn("Error deleting player from state storage, this could leak state storage memory but is usually not a fatal error")
|
||||
}
|
||||
go playerindices.DeleteMeta(context.Background(), s.pool, id)
|
||||
}
|
||||
|
||||
// GetUpdates is this service's implementation of the GetUpdates gRPC method defined in frontend.proto
|
||||
func (s *frontendAPI) GetUpdates(p *frontend.Player, assignmentStream frontend.Frontend_GetUpdatesServer) error {
|
||||
// Get cancellable context
|
||||
ctx, cancel := context.WithCancel(assignmentStream.Context())
|
||||
defer cancel()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetAssignment"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// get and return connection string
|
||||
watchChan := redispb.PlayerWatcher(ctx, s.pool, *p) // watcher() runs the appropriate Redis commands.
|
||||
timeoutChan := time.After(time.Duration(s.cfg.GetInt("api.frontend.timeout")) * time.Second)
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context cancelled
|
||||
feLog.WithFields(log.Fields{
|
||||
"playerid": p.Id,
|
||||
}).Info("client closed connection successfully")
|
||||
stats.Record(fnCtx, FeGrpcRequests.M(1))
|
||||
return nil
|
||||
case <-timeoutChan: // Timeout reached without client closing connection
|
||||
// TODO:deal with the fallout
|
||||
err := errors.New("server timeout reached without client closing connection")
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"playerid": p.Id,
|
||||
}).Error("State storage error")
|
||||
|
||||
// Count errors for metrics
|
||||
errTag, _ := tag.NewKey("errtype")
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(errTag, "watch_timeout"))
|
||||
stats.Record(fnCtx, FeGrpcErrors.M(1))
|
||||
//TODO: we could generate a frontend.player message with an error
|
||||
//field and stream it to the client before throwing the error here
|
||||
//if we wanted to send more useful client retry information
|
||||
return err
|
||||
|
||||
case a := <-watchChan:
|
||||
feLog.WithFields(log.Fields{
|
||||
"assignment": a.Assignment,
|
||||
"playerid": a.Id,
|
||||
"status": a.Status,
|
||||
"error": a.Error,
|
||||
}).Info("updating client")
|
||||
assignmentStream.Send(&a)
|
||||
stats.Record(fnCtx, FeGrpcStreamedResponses.M(1))
|
||||
// Reset timeout.
|
||||
timeoutChan = time.After(time.Duration(s.cfg.GetInt("api.frontend.timeout")) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,149 +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
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// OpenCensus Measures. These are exported as metrics to your monitoring system
|
||||
// https://godoc.org/go.opencensus.io/stats
|
||||
//
|
||||
// When making opencensus stats, the 'name' param, with forward slashes changed
|
||||
// to underscores, is appended to the 'namespace' value passed to the
|
||||
// prometheus exporter to become the Prometheus metric name. You can also look
|
||||
// into having Prometheus rewrite your metric names on scrape.
|
||||
//
|
||||
// For example:
|
||||
// - defining the promethus export namespace "open_match" when instanciating the exporter:
|
||||
// pe, err := promethus.NewExporter(promethus.Options{Namespace: "open_match"})
|
||||
// - and naming the request counter "frontend/requests_total":
|
||||
// MGrpcRequests := stats.Int64("frontendapi/requests_total", ...
|
||||
// - results in the prometheus metric name:
|
||||
// open_match_frontendapi_requests_total
|
||||
// - [note] when using opencensus views to aggregate the metrics into
|
||||
// distribution buckets and such, multiple metrics
|
||||
// will be generated with appended types ("<metric>_bucket",
|
||||
// "<metric>_count", "<metric>_sum", for example)
|
||||
//
|
||||
// In addition, OpenCensus stats propogated to Prometheus have the following
|
||||
// auto-populated labels pulled from kubernetes, which we should avoid to
|
||||
// prevent overloading and having to use the HonorLabels param in Prometheus.
|
||||
//
|
||||
// - Information about the k8s pod being monitored:
|
||||
// "pod" (name of the monitored k8s pod)
|
||||
// "namespace" (k8s namespace of the monitored pod)
|
||||
// - Information about how promethus is gathering the metrics:
|
||||
// "instance" (IP and port number being scraped by prometheus)
|
||||
// "job" (name of the k8s service being scraped by prometheus)
|
||||
// "endpoint" (name of the k8s port in the k8s service being scraped by prometheus)
|
||||
//
|
||||
var (
|
||||
// API instrumentation
|
||||
FeGrpcRequests = stats.Int64("frontendapi/requests_total", "Number of requests to the gRPC Frontend API endpoints", "1")
|
||||
FeGrpcStreamedResponses = stats.Int64("frontendapi/streamed_responses_total", "Number of responses streamed back from the gRPC Frontend API endpoints", "1")
|
||||
FeGrpcErrors = stats.Int64("frontendapi/errors_total", "Number of errors generated by the gRPC Frontend API endpoints", "1")
|
||||
FeGrpcLatencySecs = stats.Float64("frontendapi/latency_seconds", "Latency in seconds of the gRPC Frontend API endpoints", "1")
|
||||
|
||||
// Logging instrumentation
|
||||
// There's no need to record this measurement directly if you use
|
||||
// the logrus hook provided in metrics/helper.go after instantiating the
|
||||
// logrus instance in your application code.
|
||||
// https://godoc.org/github.com/sirupsen/logrus#LevelHooks
|
||||
FeLogLines = stats.Int64("frontendapi/logs_total", "Number of Frontend API lines logged", "1")
|
||||
|
||||
// Failure instrumentation
|
||||
FeFailures = stats.Int64("frontendapi/failures_total", "Number of Frontend API failures", "1")
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyMethod is used to tag a measure with the currently running API method.
|
||||
KeyMethod, _ = tag.NewKey("method")
|
||||
KeySeverity, _ = tag.NewKey("severity")
|
||||
)
|
||||
|
||||
var (
|
||||
// Latency in buckets:
|
||||
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
|
||||
latencyDistribution = view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000)
|
||||
)
|
||||
|
||||
// Package metrics provides some convience views.
|
||||
// You need to register the views for the data to actually be collected.
|
||||
// Note: The OpenCensus View 'Description' is exported to Prometheus as the HELP string.
|
||||
// Note: If you get a "Failed to export to Prometheus: inconsistent label
|
||||
// cardinality" error, chances are you forgot to set the tags specified in the
|
||||
// view for a given measure when you tried to do a stats.Record()
|
||||
var (
|
||||
FeLatencyView = &view.View{
|
||||
Name: "frontend/latency",
|
||||
Measure: FeGrpcLatencySecs,
|
||||
Description: "The distribution of frontend latencies",
|
||||
Aggregation: latencyDistribution,
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
FeRequestCountView = &view.View{
|
||||
Name: "frontend/grpc/requests",
|
||||
Measure: FeGrpcRequests,
|
||||
Description: "The number of successful frontend gRPC requests",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
FeStreamedResponseCountView = &view.View{
|
||||
Name: "frontend/grpc/streamed_responses",
|
||||
Measure: FeGrpcRequests,
|
||||
Description: "The number of successful streamed gRPC responses",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
FeErrorCountView = &view.View{
|
||||
Name: "frontend/grpc/errors",
|
||||
Measure: FeGrpcErrors,
|
||||
Description: "The number of gRPC errors",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
FeLogCountView = &view.View{
|
||||
Name: "log_lines/total",
|
||||
Measure: FeLogLines,
|
||||
Description: "The number of lines logged",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeySeverity},
|
||||
}
|
||||
|
||||
FeFailureCountView = &view.View{
|
||||
Name: "failures",
|
||||
Measure: FeFailures,
|
||||
Description: "The number of failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultFrontendAPIViews are the default frontend API OpenCensus measure views.
|
||||
var DefaultFrontendAPIViews = []*view.View{
|
||||
FeLatencyView,
|
||||
FeRequestCountView,
|
||||
FeStreamedResponseCountView,
|
||||
FeErrorCountView,
|
||||
FeLogCountView,
|
||||
FeFailureCountView,
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-base:dev' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-frontendapi:dev',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-frontendapi:dev']
|
@ -1,14 +0,0 @@
|
||||
/*
|
||||
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
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/apisrv"
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/logging"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
redishelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
feLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "frontend",
|
||||
}
|
||||
feLog = log.WithFields(feLogFields)
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
err = errors.New("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(apisrv.FeLogLines, apisrv.KeySeverity))
|
||||
|
||||
// Add a hook to the logger to log the filename & line number.
|
||||
log.SetReportCaller(true)
|
||||
|
||||
// Viper config management initialization
|
||||
cfg, err = config.Read()
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Unable to load config file")
|
||||
}
|
||||
|
||||
// Configure open match logging defaults
|
||||
logging.ConfigureLogging(cfg)
|
||||
|
||||
// Configure OpenCensus exporter to Prometheus
|
||||
// metrics.ConfigureOpenCensusPrometheusExporter expects that every OpenCensus view you
|
||||
// want to register is in an array, so append any views you want from other
|
||||
// packages to a single array here.
|
||||
ocServerViews := apisrv.DefaultFrontendAPIViews // FrontendAPI OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, ocgrpc.DefaultServerViews...) // gRPC OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, config.CfgVarCountView) // config loader view.
|
||||
// Waiting on https://github.com/opencensus-integrations/redigo/pull/1
|
||||
// ocServerViews = append(ocServerViews, redis.ObservabilityMetricViews...) // redis OpenCensus views.
|
||||
feLog.WithFields(log.Fields{"viewscount": len(ocServerViews)}).Info("Loaded OpenCensus views")
|
||||
metrics.ConfigureOpenCensusPrometheusExporter(cfg, ocServerViews)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Connect to redis
|
||||
pool := redishelpers.ConnectionPool(cfg)
|
||||
defer pool.Close()
|
||||
|
||||
// Instantiate the gRPC server with the connections we've made
|
||||
feLog.Info("Attempting to start gRPC server")
|
||||
srv := apisrv.New(cfg, pool)
|
||||
|
||||
// Run the gRPC server
|
||||
err := srv.Open()
|
||||
if err != nil {
|
||||
feLog.WithFields(log.Fields{"error": err.Error()}).Fatal("Failed to start gRPC server")
|
||||
}
|
||||
|
||||
// Exit when we see a signal
|
||||
terminate := make(chan os.Signal, 1)
|
||||
signal.Notify(terminate, os.Interrupt)
|
||||
<-terminate
|
||||
feLog.Info("Shutting down gRPC server")
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../config/matchmaker_config.json
|
25
cmd/minimatch/main.go
Normal file
25
cmd/minimatch/main.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("minimatch", minimatch.BindService)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM gcr.io/open-match-public-images/openmatch-base:dev as builder
|
||||
|
||||
# Necessary to get a specific version of the golang k8s client
|
||||
RUN go get github.com/tools/godep
|
||||
RUN go get k8s.io/client-go/...
|
||||
WORKDIR /go/src/k8s.io/client-go
|
||||
RUN git checkout v7.0.0
|
||||
RUN godep restore ./...
|
||||
RUN rm -rf vendor/
|
||||
RUN rm -rf /go/src/github.com/golang/protobuf/
|
||||
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmforc/
|
||||
COPY . .
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
# Uncomment to build production images (removes all troubleshooting tools)
|
||||
#FROM scratch
|
||||
#COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmforc/mmforc .
|
||||
CMD ["./mmforc"]
|
@ -1,10 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-base:dev' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmforc:dev',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmforc:dev']
|
@ -1,404 +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.
|
||||
*/
|
||||
|
||||
// Note: the example only works with the code within the same release/branch.
|
||||
// This is based on the example from the official k8s golang client repository:
|
||||
// k8s.io/client-go/examples/create-update-delete-deployment/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/logging"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/tidwall/gjson"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
//"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
mmforcLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmforc",
|
||||
}
|
||||
mmforcLog = log.WithFields(mmforcLogFields)
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
err = errors.New("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(MmforcLogLines, KeySeverity))
|
||||
|
||||
// Viper config management initialization
|
||||
cfg, err = config.Read()
|
||||
if err != nil {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Unable to load config file")
|
||||
}
|
||||
|
||||
// Configure open match logging defaults
|
||||
logging.ConfigureLogging(cfg)
|
||||
|
||||
// Configure OpenCensus exporter to Prometheus
|
||||
// metrics.ConfigureOpenCensusPrometheusExporter expects that every OpenCensus view you
|
||||
// want to register is in an array, so append any views you want from other
|
||||
// packages to a single array here.
|
||||
ocMmforcViews := DefaultMmforcViews // mmforc OpenCensus views.
|
||||
// Waiting on https://github.com/opencensus-integrations/redigo/pull/1
|
||||
// ocMmforcViews = append(ocMmforcViews, redis.ObservabilityMetricViews...) // redis OpenCensus views.
|
||||
mmforcLog.WithFields(log.Fields{"viewscount": len(ocMmforcViews)}).Info("Loaded OpenCensus views")
|
||||
metrics.ConfigureOpenCensusPrometheusExporter(cfg, ocMmforcViews)
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
pool := redisHelpers.ConnectionPool(cfg)
|
||||
redisConn := pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Get k8s credentials so we can starts k8s Jobs
|
||||
mmforcLog.Info("Attempting to acquire k8s credentials")
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mmforcLog.Info("K8s credentials acquired")
|
||||
|
||||
start := time.Now()
|
||||
checkProposals := true
|
||||
|
||||
// main loop; kick off matchmaker functions for profiles in the profile
|
||||
// queue and an evaluator when proposals are in the proposals queue
|
||||
for {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_ = cancel
|
||||
|
||||
// Get profiles and kick off a job for each
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"profileQueueName": cfg.GetString("queues.profiles.name"),
|
||||
"pullCount": cfg.GetInt("queues.profiles.pullCount"),
|
||||
"query": "SPOP",
|
||||
"component": "statestorage",
|
||||
}).Debug("Retreiving match profiles")
|
||||
|
||||
results, err := redis.Strings(redisConn.Do("SPOP",
|
||||
cfg.GetString("queues.profiles.name"), cfg.GetInt("queues.profiles.pullCount")))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"numProfiles": len(results),
|
||||
}).Info("Starting MMF jobs...")
|
||||
|
||||
for _, profile := range results {
|
||||
// Kick off the job asynchrnously
|
||||
go mmfunc(ctx, profile, cfg, clientset, pool)
|
||||
// Count the number of jobs running
|
||||
redisHelpers.Increment(context.Background(), pool, "concurrentMMFs")
|
||||
}
|
||||
} else {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"profileQueueName": cfg.GetString("queues.profiles.name"),
|
||||
}).Info("Unable to retreive match profiles from statestorage - have you entered any?")
|
||||
}
|
||||
|
||||
// Check to see if we should run the evaluator.
|
||||
// Get number of running MMFs
|
||||
r, err := redisHelpers.Retrieve(context.Background(), pool, "concurrentMMFs")
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "redigo: nil returned" {
|
||||
// No MMFs have run since we last evaluated; reset timer and loop
|
||||
mmforcLog.Debug("Number of concurrentMMFs is nil")
|
||||
start = time.Now()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
continue
|
||||
}
|
||||
numRunning, err := strconv.Atoi(r)
|
||||
if err != nil {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Issue retrieving number of currently running MMFs")
|
||||
}
|
||||
|
||||
// We are ready to evaluate either when all MMFs are complete, or the
|
||||
// timeout is reached.
|
||||
//
|
||||
// Tuning how frequently the evaluator runs is a complex topic and
|
||||
// probably only of interest to users running large-scale production
|
||||
// workloads with many concurrently running matchmaking functions,
|
||||
// which have some overlap in the matchmaking player pools. Suffice to
|
||||
// say that under load, this switch should almost always trigger the
|
||||
// timeout interval code path. The concurrentMMFs check to see how
|
||||
// many are still running is meant as a deadman's switch to prevent
|
||||
// waiting to run the evaluator when all your MMFs are already
|
||||
// finished.
|
||||
switch {
|
||||
case time.Since(start).Seconds() >= float64(cfg.GetInt("evaluator.interval")):
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"interval": cfg.GetInt("evaluator.interval"),
|
||||
}).Info("Maximum evaluator interval exceeded")
|
||||
checkProposals = true
|
||||
|
||||
// Opencensus tagging
|
||||
ctx, _ = tag.New(ctx, tag.Insert(KeyEvalReason, "interval_exceeded"))
|
||||
case numRunning <= 0:
|
||||
mmforcLog.Info("All MMFs complete")
|
||||
checkProposals = true
|
||||
numRunning = 0
|
||||
ctx, _ = tag.New(ctx, tag.Insert(KeyEvalReason, "mmfs_completed"))
|
||||
}
|
||||
|
||||
if checkProposals {
|
||||
// Make sure there are proposals in the queue. No need to run the
|
||||
// evaluator if there are none.
|
||||
checkProposals = false
|
||||
mmforcLog.Info("Checking statestorage for match object proposals")
|
||||
results, err := redisHelpers.Count(context.Background(), pool, cfg.GetString("queues.proposals.name"))
|
||||
switch {
|
||||
case err != nil:
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Couldn't retrieve the length of the proposal queue from statestorage!")
|
||||
case results == 0:
|
||||
mmforcLog.WithFields(log.Fields{}).Warn("No proposals in the queue!")
|
||||
default:
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"numProposals": results,
|
||||
}).Info("Proposals available, evaluating!")
|
||||
go evaluator(ctx, cfg, clientset)
|
||||
}
|
||||
err = redisHelpers.Delete(context.Background(), pool, "concurrentMMFs")
|
||||
if err != nil {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Error deleting concurrent MMF counter!")
|
||||
}
|
||||
start = time.Now()
|
||||
}
|
||||
|
||||
// TODO: Make this tunable via config.
|
||||
// A sleep here is not critical but just a useful safety valve in case
|
||||
// things are broken, to keep the main loop from going all-out and spamming the log.
|
||||
mainSleep := 1000
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"ms": mainSleep,
|
||||
}).Info("Sleeping...")
|
||||
time.Sleep(time.Duration(mainSleep) * time.Millisecond)
|
||||
} // End main for loop
|
||||
}
|
||||
|
||||
// mmfunc generates a k8s job that runs the specified mmf container image.
|
||||
// resultsID is the redis key that the Backend API is monitoring for results; we can 'short circuit' and write errors directly to this key if we can't run the MMF for some reason.
|
||||
func mmfunc(ctx context.Context, resultsID string, cfg *viper.Viper, clientset *kubernetes.Clientset, pool *redis.Pool) {
|
||||
|
||||
// Generate the various keys/names, some of which must be populated to the k8s job.
|
||||
imageName := cfg.GetString("defaultImages.mmf.name") + ":" + cfg.GetString("defaultImages.mmf.tag")
|
||||
jobType := "mmf"
|
||||
ids := strings.Split(resultsID, ".") // comes in as dot-concatinated moID and profID.
|
||||
moID := ids[0]
|
||||
profID := ids[1]
|
||||
timestamp := strconv.Itoa(int(time.Now().Unix()))
|
||||
jobName := timestamp + "." + moID + "." + profID + "." + jobType
|
||||
propID := "proposal." + timestamp + "." + moID + "." + profID
|
||||
|
||||
// Extra fields for structured logging
|
||||
lf := log.Fields{"jobName": jobName}
|
||||
if cfg.GetBool("debug") { // Log a lot more info.
|
||||
lf = log.Fields{
|
||||
"jobType": jobType,
|
||||
"backendMatchObject": moID,
|
||||
"profile": profID,
|
||||
"jobTimestamp": timestamp,
|
||||
"containerImage": imageName,
|
||||
"jobName": jobName,
|
||||
"profileImageJSONKey": cfg.GetString("jsonkeys.mmfImage"),
|
||||
}
|
||||
}
|
||||
mmfuncLog := mmforcLog.WithFields(lf)
|
||||
|
||||
// Read the full profile from redis and access any keys that are important to deciding how MMFs are run.
|
||||
// TODO: convert this to using redispb and directly access the protobuf message instead of retrieving as a map?
|
||||
profile, err := redisHelpers.RetrieveAll(ctx, pool, profID)
|
||||
if err != nil {
|
||||
// Log failure to read this profile and return - won't run an MMF for an unreadable profile.
|
||||
mmfuncLog.WithFields(log.Fields{"error": err.Error()}).Error("Failure retreiving profile from statestorage")
|
||||
return
|
||||
}
|
||||
|
||||
// Got profile from state storage, make sure it is valid
|
||||
if gjson.Valid(profile["properties"]) {
|
||||
profileImage := gjson.Get(profile["properties"], cfg.GetString("jsonkeys.mmfImage"))
|
||||
if profileImage.Exists() {
|
||||
imageName = profileImage.String()
|
||||
mmfuncLog = mmfuncLog.WithFields(log.Fields{"containerImage": imageName})
|
||||
} else {
|
||||
mmfuncLog.Warn("Failed to read image name from profile at configured json key, using default image instead")
|
||||
}
|
||||
}
|
||||
mmfuncLog.Info("Attempting to create mmf k8s job")
|
||||
|
||||
// Kick off k8s job
|
||||
envvars := []apiv1.EnvVar{
|
||||
{Name: "MMF_PROFILE_ID", Value: profID},
|
||||
{Name: "MMF_PROPOSAL_ID", Value: propID},
|
||||
{Name: "MMF_REQUEST_ID", Value: moID},
|
||||
{Name: "MMF_ERROR_ID", Value: resultsID},
|
||||
{Name: "MMF_TIMESTAMP", Value: timestamp},
|
||||
}
|
||||
err = submitJob(clientset, jobType, jobName, imageName, envvars)
|
||||
if err != nil {
|
||||
// Record failure & log
|
||||
stats.Record(ctx, mmforcMmfFailures.M(1))
|
||||
mmfuncLog.WithFields(log.Fields{"error": err.Error()}).Error("MMF job submission failure!")
|
||||
} else {
|
||||
// Record Success
|
||||
stats.Record(ctx, mmforcMmfs.M(1))
|
||||
}
|
||||
}
|
||||
|
||||
// evaluator generates a k8s job that runs the specified evaluator container image.
|
||||
func evaluator(ctx context.Context, cfg *viper.Viper, clientset *kubernetes.Clientset) {
|
||||
|
||||
imageName := cfg.GetString("defaultImages.evaluator.name") + ":" + cfg.GetString("defaultImages.evaluator.tag")
|
||||
// Generate the job name
|
||||
timestamp := strconv.Itoa(int(time.Now().Unix()))
|
||||
jobType := "evaluator"
|
||||
jobName := timestamp + "." + jobType
|
||||
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": jobName,
|
||||
"containerImage": imageName,
|
||||
}).Info("Attempting to create evaluator k8s job")
|
||||
|
||||
// Kick off k8s job
|
||||
envvars := []apiv1.EnvVar{{Name: "MMF_TIMESTAMP", Value: timestamp}}
|
||||
err = submitJob(clientset, jobType, jobName, imageName, envvars)
|
||||
if err != nil {
|
||||
// Record failure & log
|
||||
stats.Record(ctx, mmforcEvalFailures.M(1))
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"jobName": jobName,
|
||||
"containerImage": imageName,
|
||||
}).Error("Evaluator job submission failure!")
|
||||
} else {
|
||||
// Record success
|
||||
stats.Record(ctx, mmforcEvals.M(1))
|
||||
}
|
||||
}
|
||||
|
||||
// submitJob submits a job to kubernetes
|
||||
func submitJob(clientset *kubernetes.Clientset, jobType string, jobName string, imageName string, envvars []apiv1.EnvVar) error {
|
||||
|
||||
// DEPRECATED: will be removed in a future vrsion. Please switch to using the 'MMF_*' environment variables.
|
||||
v := strings.Split(jobName, ".")
|
||||
envvars = append(envvars, apiv1.EnvVar{Name: "PROFILE", Value: strings.Join(v[:len(v)-1], ".")})
|
||||
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jobName,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: int32Ptr(1),
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": jobType,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
// Unused; here as an example.
|
||||
// Later we can put things more complicated than
|
||||
// env vars here and read them using k8s downward API
|
||||
// volumes
|
||||
"profile": jobName,
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
RestartPolicy: "Never",
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: jobType,
|
||||
Image: imageName,
|
||||
ImagePullPolicy: "Always",
|
||||
Env: envvars,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Get the namespace for the job from the current namespace, otherwise, use default
|
||||
namespace := os.Getenv("METADATA_NAMESPACE")
|
||||
if len(namespace) == 0 {
|
||||
namespace = apiv1.NamespaceDefault
|
||||
}
|
||||
|
||||
// Submit kubernetes job
|
||||
jobsClient := clientset.BatchV1().Jobs(namespace)
|
||||
result, err := jobsClient.Create(job)
|
||||
if err != nil {
|
||||
// TODO: replace queued profiles if things go south
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Couldn't create k8s job!")
|
||||
}
|
||||
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": result.GetObjectMeta().GetName(),
|
||||
}).Info("Created job.")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// readability functions used by generateJobSpec
|
||||
func int32Ptr(i int32) *int32 { return &i }
|
||||
func strPtr(i string) *string { return &i }
|
@ -1 +0,0 @@
|
||||
../../config/matchmaker_config.json
|
@ -1,128 +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
|
||||
|
||||
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 (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// OpenCensus Measures. These are exported as metrics to your monitoring system
|
||||
// https://godoc.org/go.opencensus.io/stats
|
||||
//
|
||||
// When making opencensus stats, the 'name' param, with forward slashes changed
|
||||
// to underscores, is appended to the 'namespace' value passed to the
|
||||
// prometheus exporter to become the Prometheus metric name. You can also look
|
||||
// into having Prometheus rewrite your metric names on scrape.
|
||||
//
|
||||
// For example:
|
||||
// - defining the promethus export namespace "open_match" when instanciating the exporter:
|
||||
// pe, err := promethus.NewExporter(promethus.Options{Namespace: "open_match"})
|
||||
// - and naming the request counter "backend/requests_total":
|
||||
// MGrpcRequests := stats.Int64("backendapi/requests_total", ...
|
||||
// - results in the prometheus metric name:
|
||||
// open_match_backendapi_requests_total
|
||||
// - [note] when using opencensus views to aggregate the metrics into
|
||||
// distribution buckets and such, multiple metrics
|
||||
// will be generated with appended types ("<metric>_bucket",
|
||||
// "<metric>_count", "<metric>_sum", for example)
|
||||
//
|
||||
// In addition, OpenCensus stats propogated to Prometheus have the following
|
||||
// auto-populated labels pulled from kubernetes, which we should avoid to
|
||||
// prevent overloading and having to use the HonorLabels param in Prometheus.
|
||||
//
|
||||
// - Information about the k8s pod being monitored:
|
||||
// "pod" (name of the monitored k8s pod)
|
||||
// "namespace" (k8s namespace of the monitored pod)
|
||||
// - Information about how promethus is gathering the metrics:
|
||||
// "instance" (IP and port number being scraped by prometheus)
|
||||
// "job" (name of the k8s service being scraped by prometheus)
|
||||
// "endpoint" (name of the k8s port in the k8s service being scraped by prometheus)
|
||||
//
|
||||
var (
|
||||
// Logging instrumentation
|
||||
// There's no need to record this measurement directly if you use
|
||||
// the logrus hook provided in metrics/helper.go after instantiating the
|
||||
// logrus instance in your application code.
|
||||
// https://godoc.org/github.com/sirupsen/logrus#LevelHooks
|
||||
MmforcLogLines = stats.Int64("mmforc/logs_total", "Number of Backend API lines logged", "1")
|
||||
|
||||
// Counting operations
|
||||
mmforcMmfs = stats.Int64("mmforc/mmfs_total", "Number of mmf jobs submitted to kubernetes", "1")
|
||||
mmforcMmfFailures = stats.Int64("mmforc/mmf/failures_total", "Number of failures attempting to submit mmf jobs to kubernetes", "1")
|
||||
mmforcEvals = stats.Int64("mmforc/evaluators_total", "Number of evaluator jobs submitted to kubernetes", "1")
|
||||
mmforcEvalFailures = stats.Int64("mmforc/evaluator/failures_total", "Number of failures attempting to submit evaluator jobs to kubernetes", "1")
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyEvalReason is used to tag which code path caused the evaluator to run.
|
||||
KeyEvalReason, _ = tag.NewKey("evalReason")
|
||||
// KeySeverity is used to tag a the severity of a log message.
|
||||
KeySeverity, _ = tag.NewKey("severity")
|
||||
)
|
||||
|
||||
var (
|
||||
// Latency in buckets:
|
||||
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
|
||||
latencyDistribution = view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000)
|
||||
)
|
||||
|
||||
// Package metrics provides some convience views.
|
||||
// You need to register the views for the data to actually be collected.
|
||||
// Note: The OpenCensus View 'Description' is exported to Prometheus as the HELP string.
|
||||
// Note: If you get a "Failed to export to Prometheus: inconsistent label
|
||||
// cardinality" error, chances are you forgot to set the tags specified in the
|
||||
// view for a given measure when you tried to do a stats.Record()
|
||||
var (
|
||||
mmforcMmfsCountView = &view.View{
|
||||
Name: "mmforc/mmfs",
|
||||
Measure: mmforcMmfs,
|
||||
Description: "The number of mmf jobs submitted to kubernetes",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
mmforcMmfFailuresCountView = &view.View{
|
||||
Name: "mmforc/mmf/failures",
|
||||
Measure: mmforcMmfFailures,
|
||||
Description: "The number of mmf jobs that failed submission to kubernetes",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
mmforcEvalsCountView = &view.View{
|
||||
Name: "mmforc/evaluators",
|
||||
Measure: mmforcEvals,
|
||||
Description: "The number of evaluator jobs submitted to kubernetes",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyEvalReason},
|
||||
}
|
||||
|
||||
mmforcEvalFailuresCountView = &view.View{
|
||||
Name: "mmforc/evaluator/failures",
|
||||
Measure: mmforcEvalFailures,
|
||||
Description: "The number of evaluator jobs that failed submission to kubernetes",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyEvalReason},
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultMmforcViews are the default matchmaker orchestrator OpenCensus measure views.
|
||||
var DefaultMmforcViews = []*view.View{
|
||||
mmforcEvalsCountView,
|
||||
mmforcMmfFailuresCountView,
|
||||
mmforcMmfsCountView,
|
||||
mmforcEvalFailuresCountView,
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# Golang application builder steps
|
||||
FROM gcr.io/open-match-public-images/openmatch-base:dev as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi
|
||||
COPY . .
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
#FROM scratch
|
||||
#COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/frontendapi .
|
||||
ENTRYPOINT ["./mmlogicapi"]
|
@ -1,596 +0,0 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/mmlogic.proto.
|
||||
Most of the documentation for what these calls should do is in that file!
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
mmlogic "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/set"
|
||||
redishelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/ignorelist"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
mlLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic",
|
||||
}
|
||||
mlLog = log.WithFields(mlLogFields)
|
||||
)
|
||||
|
||||
// MmlogicAPI implements mmlogic.ApiServer, the server generated by compiling
|
||||
// the protobuf, by fulfilling the mmlogic.APIClient interface.
|
||||
type MmlogicAPI struct {
|
||||
grpc *grpc.Server
|
||||
cfg *viper.Viper
|
||||
pool *redis.Pool
|
||||
}
|
||||
type mmlogicAPI MmlogicAPI
|
||||
|
||||
// New returns an instantiated srvice
|
||||
func New(cfg *viper.Viper, pool *redis.Pool) *MmlogicAPI {
|
||||
s := MmlogicAPI{
|
||||
pool: pool,
|
||||
grpc: grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{})),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(MlLogLines, KeySeverity))
|
||||
|
||||
// Register gRPC server
|
||||
mmlogic.RegisterMmLogicServer(s.grpc, (*mmlogicAPI)(&s))
|
||||
mlLog.Info("Successfully registered gRPC server")
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *MmlogicAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.mmlogic.port"))
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"port": s.cfg.GetInt("api.mmlogic.port"),
|
||||
}).Error("net.Listen() error")
|
||||
return err
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"port": s.cfg.GetInt("api.mmlogic.port")}).Info("TCP net listener initialized")
|
||||
|
||||
go func() {
|
||||
err := s.grpc.Serve(ln)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Error("gRPC serve() error")
|
||||
}
|
||||
mlLog.Info("serving gRPC endpoints")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProfile is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) GetProfile(c context.Context, profile *mmlogic.MatchObject) (*mmlogic.MatchObject, error) {
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetProfile"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Get profile.
|
||||
mlLog.WithFields(log.Fields{"profileid": profile.Id}).Info("Attempting retreival of profile")
|
||||
err := redispb.UnmarshalFromRedis(c, s.pool, profile)
|
||||
mlLog.Warn("returned profile from redispb", profile)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"profileid": profile.Id,
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return profile, err
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"profileid": profile.Id}).Debug("Retrieved profile from state storage")
|
||||
|
||||
mlLog.Debug(profile)
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
//return out, err
|
||||
return profile, err
|
||||
|
||||
}
|
||||
|
||||
// CreateProposal is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) CreateProposal(c context.Context, prop *mmlogic.MatchObject) (*mmlogic.Result, error) {
|
||||
|
||||
// Retreive configured redis keys.
|
||||
list := "proposed"
|
||||
proposalq := s.cfg.GetString("queues.proposals.name")
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreateProposal"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Log what kind of results we received.
|
||||
cpLog := mlLog.WithFields(log.Fields{"id": prop.Id})
|
||||
if len(prop.Error) == 0 {
|
||||
cpLog.Info("writing MMF propsal to state storage")
|
||||
} else {
|
||||
cpLog.Info("writing MMF error to state storage")
|
||||
}
|
||||
|
||||
// Write all non-id fields from the protobuf message to state storage.
|
||||
err := redispb.MarshalToRedis(c, s.pool, prop, s.cfg.GetInt("redis.expirations.matchobject"))
|
||||
if err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Proposals need two more actions: players added to ignorelist, and adding
|
||||
// the proposalkey to the proposal queue for the evaluator to read.
|
||||
if len(prop.Error) == 0 {
|
||||
// look for players to add to the ignorelist
|
||||
cpLog.Info("parsing rosters")
|
||||
playerIDs := make([]string, 0)
|
||||
for _, roster := range prop.Rosters {
|
||||
playerIDs = append(playerIDs, getPlayerIdsFromRoster(roster)...)
|
||||
}
|
||||
|
||||
// If players were on the roster, add them to the ignorelist
|
||||
if len(playerIDs) > 0 {
|
||||
cpLog.WithFields(log.Fields{
|
||||
"count": len(playerIDs),
|
||||
"ignorelist": list,
|
||||
}).Info("adding players to ignorelist")
|
||||
|
||||
err := ignorelist.Add(redisConn, list, playerIDs)
|
||||
if err != nil {
|
||||
cpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"ignorelist": list,
|
||||
}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
} else {
|
||||
cpLog.Warn("found no players in rosters, not adding any players to the proposed ignorelist")
|
||||
}
|
||||
|
||||
// add propkey to proposalsq
|
||||
pqLog := cpLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"queue": proposalq,
|
||||
})
|
||||
pqLog.Info("adding propsal to queue")
|
||||
|
||||
_, err = redisConn.Do("SADD", proposalq, prop.Id)
|
||||
if err != nil {
|
||||
pqLog.WithFields(log.Fields{"error": err.Error()}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this MMF as finished by decrementing the concurrent MMFs.
|
||||
// This is used to trigger the evaluator early if all MMFs have finished
|
||||
// before its next scheduled run.
|
||||
cmLog := cpLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"key": "concurrentMMFs",
|
||||
})
|
||||
cmLog.Info("marking MMF finished for evaluator")
|
||||
_, err = redishelpers.Decrement(fnCtx, s.pool, "concurrentMMFs")
|
||||
if err != nil {
|
||||
cmLog.WithFields(log.Fields{"error": err.Error()}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return &mmlogic.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
// GetPlayerPool is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
// API_GetPlayerPoolServer returns mutiple PlayerPool messages - they should
|
||||
// all be reassembled into one set on the calling side, as they are just
|
||||
// paginated subsets of the player pool.
|
||||
func (s *mmlogicAPI) GetPlayerPool(pool *mmlogic.PlayerPool, stream mmlogic.MmLogic_GetPlayerPoolServer) error {
|
||||
|
||||
// TODO: quit if context is cancelled
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetPlayerPool"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
mlLog.WithFields(log.Fields{
|
||||
"filterCount": len(pool.Filters),
|
||||
"pool": pool.Name,
|
||||
"funcName": funcName,
|
||||
}).Info("attempting to retreive player pool from state storage")
|
||||
|
||||
// One working Roster per filter in the set. Combined at the end.
|
||||
filteredRosters := make(map[string][]string)
|
||||
// Temp store the results so we can also populate some field values in the final return roster.
|
||||
filteredResults := make(map[string]map[string]int64)
|
||||
overlap := make([]string, 0)
|
||||
fnStart := time.Now()
|
||||
|
||||
// Loop over all filters, get results, combine
|
||||
for _, thisFilter := range pool.Filters {
|
||||
|
||||
filterStart := time.Now()
|
||||
results, err := s.applyFilter(ctx, thisFilter)
|
||||
thisFilter.Stats = &mmlogic.Stats{Count: int64(len(results)), Elapsed: time.Since(filterStart).Seconds()}
|
||||
mlLog.WithFields(log.Fields{
|
||||
"count": int64(len(results)),
|
||||
"elapsed": time.Since(filterStart).Seconds(),
|
||||
"filterName": thisFilter.Name,
|
||||
}).Debug("Filter stats")
|
||||
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error(), "filterName": thisFilter.Name}).Debug("Error applying filter")
|
||||
|
||||
if len(results) == 0 {
|
||||
// One simple optimization here: check the count returned by a
|
||||
// ZCOUNT query for each filter before doing anything. If any of the
|
||||
// filters return a ZCOUNT of 0, then the logical AND of all filters will
|
||||
// container no players and we can shortcircuit and quit.
|
||||
mlLog.WithFields(log.Fields{
|
||||
"count": 0,
|
||||
"filterName": thisFilter.Name,
|
||||
"pool": pool.Name,
|
||||
}).Warn("returning empty pool")
|
||||
|
||||
// Fill in the stats for this player pool.
|
||||
pool.Stats = &mmlogic.Stats{Count: int64(len(results)), Elapsed: time.Since(filterStart).Seconds()}
|
||||
|
||||
// Send the empty pool and exit.
|
||||
if err = stream.Send(pool); err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Make an array of only the player IDs; used to do set.Unions and find the
|
||||
// logical AND
|
||||
m := make([]string, len(results))
|
||||
i := 0
|
||||
for playerID := range results {
|
||||
m[i] = playerID
|
||||
i++
|
||||
}
|
||||
|
||||
// Store the array of player IDs as well as the full results for later
|
||||
// retrieval
|
||||
filteredRosters[thisFilter.Attribute] = m
|
||||
filteredResults[thisFilter.Attribute] = results
|
||||
overlap = m
|
||||
}
|
||||
|
||||
// Player must be in every filtered pool to be returned
|
||||
for field, thesePlayers := range filteredRosters {
|
||||
overlap = set.Intersection(overlap, thesePlayers)
|
||||
|
||||
_ = field
|
||||
//mlLog.WithFields(log.Fields{"count": len(overlap), "field": field}).Debug("Amount of overlap")
|
||||
}
|
||||
|
||||
// Get contents of all ignore lists and remove those players from the pool.
|
||||
il, err := s.allIgnoreLists(ctx, &mmlogic.IlInput{})
|
||||
if err != nil {
|
||||
mlLog.Error(err)
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"count": len(overlap)}).Debug("Pool size before applying ignorelists")
|
||||
mlLog.WithFields(log.Fields{"count": len(il)}).Debug("Ignorelist size")
|
||||
playerList := set.Difference(overlap, il) // removes ignorelist from the Roster
|
||||
mlLog.WithFields(log.Fields{"count": len(playerList)}).Debug("Final Pool size")
|
||||
|
||||
// Reformat the playerList as a gRPC PlayerPool message. Send partial results as we go.
|
||||
// This is pretty agressive in the partial result 'page'
|
||||
// sizes it sends, and that is partially because it assumes you're running
|
||||
// everything on a local network. If you aren't, you may need to tune this
|
||||
// pageSize.
|
||||
pageSize := s.cfg.GetInt("redis.results.pageSize")
|
||||
pageCount := int(math.Ceil((float64(len(playerList)) / float64(pageSize)))) // Divides and rounds up on any remainder
|
||||
//TODO: change if removing filtersets from rosters in favor of it being in pools
|
||||
partialRoster := mmlogic.Roster{Name: fmt.Sprintf("%v.partialRoster", pool.Name)}
|
||||
pool.Stats = &mmlogic.Stats{Count: int64(len(playerList)), Elapsed: time.Since(fnStart).Seconds()}
|
||||
for i := 0; i < len(playerList); i++ {
|
||||
// Add one additional player result to the partial pool.
|
||||
player := &mmlogic.Player{Id: playerList[i], Attributes: []*mmlogic.Player_Attribute{}}
|
||||
// Collect all the filtered attributes into the player protobuf.
|
||||
for attribute, fr := range filteredResults {
|
||||
if value, ok := fr[playerList[i]]; ok {
|
||||
player.Attributes = append(player.Attributes, &mmlogic.Player_Attribute{Name: attribute, Value: value})
|
||||
}
|
||||
}
|
||||
partialRoster.Players = append(partialRoster.Players, player)
|
||||
|
||||
// Check if we've filled in enough players to fill a page of results.
|
||||
if ((i+1)%pageSize == 0) || i == (len(playerList)-1) {
|
||||
pageName := fmt.Sprintf("%v.page%v/%v", pool.Name, i/pageSize+1, pageCount)
|
||||
poolChunk := &mmlogic.PlayerPool{
|
||||
Name: pageName,
|
||||
Filters: pool.Filters,
|
||||
Stats: pool.Stats,
|
||||
Roster: &partialRoster,
|
||||
}
|
||||
if err = stream.Send(poolChunk); err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
partialRoster.Players = []*mmlogic.Player{}
|
||||
}
|
||||
}
|
||||
|
||||
mlLog.WithFields(log.Fields{"count": len(playerList), "pool": pool.Name}).Debug("player pool streaming complete")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyFilter is a sequential query of every entry in the Redis sorted set
|
||||
// that fall beween the minimum and maximum values passed in through the filter
|
||||
// argument. This can be likely sped up later using concurrent access, but
|
||||
// with small enough player pools (less than the 'redis.queryArgs.count' config
|
||||
// parameter) the amount of work is identical, so this is fine as a starting point.
|
||||
// If the provided field is not indexed or the provided range is too large, a nil result
|
||||
// is returned and this filter should be disregarded when applying filter overlaps.
|
||||
func (s *mmlogicAPI) applyFilter(c context.Context, filter *mmlogic.Filter) (map[string]int64, error) {
|
||||
|
||||
type pName string
|
||||
pool := make(map[string]int64)
|
||||
|
||||
// Default maximum value is positive infinity (i.e. highest possible number in redis)
|
||||
// https://redis.io/commands/zrangebyscore
|
||||
maxv := strconv.FormatInt(filter.Maxv, 10) // Convert int64 to a string
|
||||
if filter.Maxv == 0 { // No max specified, set to +inf
|
||||
maxv = "+inf"
|
||||
}
|
||||
|
||||
mlLog.WithFields(log.Fields{"filterField": filter.Attribute}).Debug("In applyFilter")
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Check how many expected matches for this filter before we start retrieving.
|
||||
cmd := "ZCOUNT"
|
||||
count, err := redis.Int64(redisConn.Do(cmd, filter.Attribute, filter.Minv, maxv))
|
||||
//DEBUG: count, err := redis.Int64(redisConn.Do(cmd, "BLARG", filter.Minv, maxv))
|
||||
mlLog := mlLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"field": filter.Attribute,
|
||||
"minv": filter.Minv,
|
||||
"maxv": maxv,
|
||||
"count": count,
|
||||
})
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Error("state storage error")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
err = errors.New("filter applies to no players")
|
||||
mlLog.Error(err.Error())
|
||||
return nil, err
|
||||
} else if count > 500000 {
|
||||
// 500,000 results is an arbitrary number; OM doesn't encourage
|
||||
// patterns where MMFs look at this large of a pool.
|
||||
err = errors.New("filter applies to too many players")
|
||||
mlLog.Error(err.Error())
|
||||
for i := 0; i < int(count); i++ {
|
||||
// Send back an empty pool, used by the calling function to calculate the number of results
|
||||
pool[strconv.Itoa(i)] = 0
|
||||
}
|
||||
return pool, err
|
||||
} else if count < 100000 {
|
||||
mlLog.Info("filter processed")
|
||||
} else {
|
||||
// Send a warning to the logs.
|
||||
mlLog.Warn("filter applies to a large number of players")
|
||||
}
|
||||
|
||||
// Amount of results look okay and no redis error, begin
|
||||
// var init for player retrieval
|
||||
cmd = "ZRANGEBYSCORE"
|
||||
offset := 0
|
||||
|
||||
// Loop, retrieving players in chunks.
|
||||
for len(pool) == offset {
|
||||
results, err := redis.Int64Map(redisConn.Do(cmd, filter.Attribute, filter.Minv, maxv, "WITHSCORES", "LIMIT", offset, s.cfg.GetInt("redis.queryArgs.count")))
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"field": filter.Attribute,
|
||||
"minv": filter.Minv,
|
||||
"maxv": maxv,
|
||||
"offset": offset,
|
||||
"count": s.cfg.GetInt("redis.queryArgs.count"),
|
||||
"error": err.Error(),
|
||||
}).Error("statestorage error")
|
||||
}
|
||||
|
||||
// Increment the offset for the next query by the 'count' config value
|
||||
offset = offset + s.cfg.GetInt("redis.queryArgs.count")
|
||||
|
||||
// Add all results to this player pool
|
||||
for k, v := range results {
|
||||
if _, ok := pool[k]; ok {
|
||||
// Redis returned the same player more than once; this is not
|
||||
// actually a problem, it just indicates that players are being
|
||||
// added/removed from the index as it is queried. We take the
|
||||
// tradeoff in consistency for speed, as it won't cause issues
|
||||
// in matchmaking results as long as ignorelists are respected.
|
||||
offset--
|
||||
}
|
||||
pool[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Log completion and return
|
||||
//mlLog.WithFields(log.Fields{
|
||||
// "poolSize": len(pool),
|
||||
// "field": filter.Attribute,
|
||||
// "minv": filter.Minv,
|
||||
// "maxv": maxv,
|
||||
//}).Debug("Player pool filter processed")
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// GetAllIgnoredPlayers is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
// This is a wrapper around allIgnoreLists, and converts the []string return
|
||||
// value of that function to a gRPC Roster message to send out over the wire.
|
||||
func (s *mmlogicAPI) GetAllIgnoredPlayers(c context.Context, in *mmlogic.IlInput) (*mmlogic.Roster, error) {
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetAllIgnoredPlayers"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
il, err := s.allIgnoreLists(c, in)
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return createRosterfromPlayerIds(il), err
|
||||
}
|
||||
|
||||
// ListIgnoredPlayers is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) ListIgnoredPlayers(c context.Context, olderThan *mmlogic.IlInput) (*mmlogic.Roster, error) {
|
||||
|
||||
// TODO: is this supposed to able to take any list?
|
||||
ilName := "proposed"
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "ListIgnoredPlayers"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
mlLog.WithFields(log.Fields{"ignorelist": ilName}).Info("Attempting to get ignorelist")
|
||||
|
||||
// retreive ignore list
|
||||
il, err := ignorelist.Retrieve(redisConn, s.cfg, ilName)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"key": ilName,
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Roster{}, err
|
||||
}
|
||||
// TODO: fix this
|
||||
mlLog.Debug(fmt.Sprintf("Retreival success %v", il))
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return createRosterfromPlayerIds(il), err
|
||||
}
|
||||
|
||||
// allIgnoreLists combines all the ignore lists and returns them.
|
||||
func (s *mmlogicAPI) allIgnoreLists(c context.Context, in *mmlogic.IlInput) (allIgnored []string, err error) {
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
mlLog.Info("Attempting to get and combine ignorelists")
|
||||
|
||||
// Loop through all ignorelists configured in the config file.
|
||||
for il := range s.cfg.GetStringMap("ignoreLists") {
|
||||
ilCfg := s.cfg.Sub(fmt.Sprintf("ignoreLists.%v", il))
|
||||
thisIl, err := ignorelist.Retrieve(redisConn, ilCfg, il)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Join this ignorelist to the others we've retrieved
|
||||
allIgnored = set.Union(allIgnored, thisIl)
|
||||
}
|
||||
|
||||
return allIgnored, err
|
||||
}
|
||||
|
||||
// Functions for getting or setting player IDs to/from rosters
|
||||
// Probably should get moved to an internal module in a future version.
|
||||
func getPlayerIdsFromRoster(r *mmlogic.Roster) []string {
|
||||
playerIDs := make([]string, 0)
|
||||
for _, p := range r.Players {
|
||||
playerIDs = append(playerIDs, p.Id)
|
||||
}
|
||||
return playerIDs
|
||||
|
||||
}
|
||||
|
||||
func createRosterfromPlayerIds(playerIDs []string) *mmlogic.Roster {
|
||||
|
||||
players := make([]*mmlogic.Player, 0)
|
||||
for _, id := range playerIDs {
|
||||
players = append(players, &mmlogic.Player{Id: id})
|
||||
}
|
||||
return &mmlogic.Roster{Players: players}
|
||||
|
||||
}
|
@ -1,139 +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
|
||||
|
||||
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 apisrv
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// OpenCensus Measures. These are exported as metrics to your monitoring system
|
||||
// https://godoc.org/go.opencensus.io/stats
|
||||
//
|
||||
// When making opencensus stats, the 'name' param, with forward slashes changed
|
||||
// to underscores, is appended to the 'namespace' value passed to the
|
||||
// prometheus exporter to become the Prometheus metric name. You can also look
|
||||
// into having Prometheus rewrite your metric names on scrape.
|
||||
//
|
||||
// For example:
|
||||
// - defining the promethus export namespace "open_match" when instanciating the exporter:
|
||||
// pe, err := promethus.NewExporter(promethus.Options{Namespace: "open_match"})
|
||||
// - and naming the request counter "mmlogic/requests_total":
|
||||
// MGrpcRequests := stats.Int64("mmlogicapi/requests_total", ...
|
||||
// - results in the prometheus metric name:
|
||||
// open_match_mmlogicapi_requests_total
|
||||
// - [note] when using opencensus views to aggregate the metrics into
|
||||
// distribution buckets and such, multiple metrics
|
||||
// will be generated with appended types ("<metric>_bucket",
|
||||
// "<metric>_count", "<metric>_sum", for example)
|
||||
//
|
||||
// In addition, OpenCensus stats propogated to Prometheus have the following
|
||||
// auto-populated labels pulled from kubernetes, which we should avoid to
|
||||
// prevent overloading and having to use the HonorLabels param in Prometheus.
|
||||
//
|
||||
// - Information about the k8s pod being monitored:
|
||||
// "pod" (name of the monitored k8s pod)
|
||||
// "namespace" (k8s namespace of the monitored pod)
|
||||
// - Information about how promethus is gathering the metrics:
|
||||
// "instance" (IP and port number being scraped by prometheus)
|
||||
// "job" (name of the k8s service being scraped by prometheus)
|
||||
// "endpoint" (name of the k8s port in the k8s service being scraped by prometheus)
|
||||
//
|
||||
var (
|
||||
// API instrumentation
|
||||
MlGrpcRequests = stats.Int64("mmlogicapi/requests_total", "Number of requests to the gRPC Frontend API endpoints", "1")
|
||||
MlGrpcErrors = stats.Int64("mmlogicapi/errors_total", "Number of errors generated by the gRPC Frontend API endpoints", "1")
|
||||
MlGrpcLatencySecs = stats.Float64("mmlogicapi/latency_seconds", "Latency in seconds of the gRPC Frontend API endpoints", "1")
|
||||
|
||||
// Logging instrumentation
|
||||
// There's no need to record this measurement directly if you use
|
||||
// the logrus hook provided in metrics/helper.go after instantiating the
|
||||
// logrus instance in your application code.
|
||||
// https://godoc.org/github.com/sirupsen/logrus#LevelHooks
|
||||
MlLogLines = stats.Int64("mmlogicapi/logs_total", "Number of Frontend API lines logged", "1")
|
||||
|
||||
// Failure instrumentation
|
||||
MlFailures = stats.Int64("mmlogicapi/failures_total", "Number of Frontend API failures", "1")
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyMethod is used to tag a measure with the currently running API method.
|
||||
KeyMethod, _ = tag.NewKey("method")
|
||||
KeySeverity, _ = tag.NewKey("severity")
|
||||
)
|
||||
|
||||
var (
|
||||
// Latency in buckets:
|
||||
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
|
||||
latencyDistribution = view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000)
|
||||
)
|
||||
|
||||
// Package metrics provides some convience views.
|
||||
// You need to register the views for the data to actually be collected.
|
||||
// Note: The OpenCensus View 'Description' is exported to Prometheus as the HELP string.
|
||||
// Note: If you get a "Failed to export to Prometheus: inconsistent label
|
||||
// cardinality" error, chances are you forgot to set the tags specified in the
|
||||
// view for a given measure when you tried to do a stats.Record()
|
||||
var (
|
||||
MlLatencyView = &view.View{
|
||||
Name: "mmlogic/latency",
|
||||
Measure: MlGrpcLatencySecs,
|
||||
Description: "The distribution of mmlogic latencies",
|
||||
Aggregation: latencyDistribution,
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlRequestCountView = &view.View{
|
||||
Name: "mmlogic/grpc/requests",
|
||||
Measure: MlGrpcRequests,
|
||||
Description: "The number of successful mmlogic gRPC requests",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlErrorCountView = &view.View{
|
||||
Name: "mmlogic/grpc/errors",
|
||||
Measure: MlGrpcErrors,
|
||||
Description: "The number of gRPC errors",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlLogCountView = &view.View{
|
||||
Name: "log_lines/total",
|
||||
Measure: MlLogLines,
|
||||
Description: "The number of lines logged",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeySeverity},
|
||||
}
|
||||
|
||||
MlFailureCountView = &view.View{
|
||||
Name: "failures",
|
||||
Measure: MlFailures,
|
||||
Description: "The number of failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultMmlogicAPIViews are the default mmlogic API OpenCensus measure views.
|
||||
var DefaultMmlogicAPIViews = []*view.View{
|
||||
MlLatencyView,
|
||||
MlRequestCountView,
|
||||
MlErrorCountView,
|
||||
MlLogCountView,
|
||||
MlFailureCountView,
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-base:dev' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmlogicapi:dev',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmlogicapi:dev']
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi/apisrv"
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
mlLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic",
|
||||
}
|
||||
mlLog = log.WithFields(mlLogFields)
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
err = errors.New("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Logrus structured logging initialization
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(apisrv.MlLogLines, apisrv.KeySeverity))
|
||||
|
||||
// Viper config management initialization
|
||||
cfg, err = config.Read()
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Unable to load config file")
|
||||
}
|
||||
|
||||
if cfg.GetBool("debug") == true {
|
||||
log.SetLevel(log.DebugLevel) // debug only, verbose - turn off in production!
|
||||
mlLog.Warn("Debug logging configured. Not recommended for production!")
|
||||
}
|
||||
|
||||
// Configure OpenCensus exporter to Prometheus
|
||||
// metrics.ConfigureOpenCensusPrometheusExporter expects that every OpenCensus view you
|
||||
// want to register is in an array, so append any views you want from other
|
||||
// packages to a single array here.
|
||||
ocServerViews := apisrv.DefaultMmlogicAPIViews // Matchmaking logic API OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, ocgrpc.DefaultServerViews...) // gRPC OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, config.CfgVarCountView) // config loader view.
|
||||
// Waiting on https://github.com/opencensus-integrations/redigo/pull/1
|
||||
// ocServerViews = append(ocServerViews, redis.ObservabilityMetricViews...) // redis OpenCensus views.
|
||||
mlLog.WithFields(log.Fields{"viewscount": len(ocServerViews)}).Info("Loaded OpenCensus views")
|
||||
metrics.ConfigureOpenCensusPrometheusExporter(cfg, ocServerViews)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Connect to redis
|
||||
pool := redisHelpers.ConnectionPool(cfg)
|
||||
defer pool.Close()
|
||||
|
||||
// Instantiate the gRPC server with the connections we've made
|
||||
mlLog.Info("Attempting to start gRPC server")
|
||||
srv := apisrv.New(cfg, pool)
|
||||
|
||||
// Run the gRPC server
|
||||
err := srv.Open()
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Fatal("Failed to start gRPC server")
|
||||
}
|
||||
|
||||
// Exit when we see a signal
|
||||
terminate := make(chan os.Signal, 1)
|
||||
signal.Notify(terminate, os.Interrupt)
|
||||
<-terminate
|
||||
mlLog.Info("Shutting down gRPC server")
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../config/matchmaker_config.json
|
25
cmd/query/query.go
Normal file
25
cmd/query/query.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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)
|
||||
}
|
24
cmd/scale-backend/main.go
Normal file
24
cmd/scale-backend/main.go
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.
|
||||
|
||||
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)
|
||||
}
|
22
cmd/scale-evaluator/main.go
Normal file
22
cmd/scale-evaluator/main.go
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
24
cmd/scale-frontend/main.go
Normal file
24
cmd/scale-frontend/main.go
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.
|
||||
|
||||
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)
|
||||
}
|
23
cmd/scale-mmf/main.go
Normal file
23
cmd/scale-mmf/main.go
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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
scaleMmf "open-match.dev/open-match/examples/scale/mmf"
|
||||
)
|
||||
|
||||
func main() {
|
||||
scaleMmf.Run()
|
||||
}
|
10
cmd/swaggerui/config.json
Normal file
10
cmd/swaggerui/config.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"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"}
|
||||
]
|
||||
}
|
24
cmd/swaggerui/swaggerui.go
Normal file
24
cmd/swaggerui/swaggerui.go
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.
|
||||
|
||||
// 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()
|
||||
}
|
25
cmd/synchronizer/synchronizer.go
Normal file
25
cmd/synchronizer/synchronizer.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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)
|
||||
}
|
120
config/config.go
120
config/config.go
@ -1,120 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
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
|
||||
envMappings = map[string]string{
|
||||
"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",
|
||||
}
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
|
||||
// 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() (*viper.Viper, error) {
|
||||
|
||||
// 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(".")
|
||||
|
||||
// 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.
|
||||
return cfg, err
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
{
|
||||
"debug": true,
|
||||
"logging":{
|
||||
"level": "debug",
|
||||
"format": "text",
|
||||
"source": true
|
||||
},
|
||||
"api": {
|
||||
"backend": {
|
||||
"hostname": "om-backendapi",
|
||||
"port": 50505,
|
||||
"timeout": 90
|
||||
},
|
||||
"frontend": {
|
||||
"hostname": "om-frontendapi",
|
||||
"port": 50504,
|
||||
"timeout": 300
|
||||
},
|
||||
"mmlogic": {
|
||||
"hostname": "om-mmlogicapi",
|
||||
"port": 50503
|
||||
}
|
||||
},
|
||||
"evalutor": {
|
||||
"interval": 10
|
||||
},
|
||||
"metrics": {
|
||||
"port": 9555,
|
||||
"endpoint": "/metrics",
|
||||
"reportingPeriod": 5
|
||||
},
|
||||
"queues": {
|
||||
"profiles": {
|
||||
"name": "profileq",
|
||||
"pullCount": 100
|
||||
},
|
||||
"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
|
||||
}
|
||||
},
|
||||
"defaultImages": {
|
||||
"evaluator": {
|
||||
"name": "gcr.io/open-match-public-images/openmatch-evaluator",
|
||||
"tag": "dev"
|
||||
},
|
||||
"mmf": {
|
||||
"name": "gcr.io/open-match-public-images/openmatch-mmf-py3-mmlogic-simple",
|
||||
"tag": "dev"
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"user": "",
|
||||
"password": "",
|
||||
"pool" : {
|
||||
"maxIdle" : 3,
|
||||
"maxActive" : 0,
|
||||
"idleTimeout" : 60
|
||||
},
|
||||
"queryArgs":{
|
||||
"count": 10000
|
||||
},
|
||||
"results": {
|
||||
"pageSize": 10000
|
||||
},
|
||||
"expirations": {
|
||||
"player": 43200,
|
||||
"matchobject":43200
|
||||
}
|
||||
},
|
||||
"jsonkeys": {
|
||||
"mmfImage": "imagename",
|
||||
"rosters": "properties.rosters",
|
||||
"pools": "properties.pools"
|
||||
},
|
||||
"playerIndices": [
|
||||
"char.cleric",
|
||||
"char.knight",
|
||||
"char.paladin",
|
||||
"map.aleroth",
|
||||
"map.oasis",
|
||||
"mmr.rating",
|
||||
"mode.battleroyale",
|
||||
"mode.ctf",
|
||||
"region.europe-east1",
|
||||
"region.europe-west1",
|
||||
"region.europe-west2",
|
||||
"region.europe-west3",
|
||||
"region.europe-west4",
|
||||
"role.dps",
|
||||
"role.support",
|
||||
"role.tank"
|
||||
]
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"apiVersion":"extensions/v1beta1",
|
||||
"kind":"Deployment",
|
||||
"metadata":{
|
||||
"name":"om-backendapi",
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "backend"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"replicas":1,
|
||||
"selector":{
|
||||
"matchLabels":{
|
||||
"app":"openmatch",
|
||||
"component": "backend"
|
||||
}
|
||||
},
|
||||
"template":{
|
||||
"metadata":{
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "backend"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"containers":[
|
||||
{
|
||||
"name":"om-backend",
|
||||
"image":"gcr.io/open-match-public-images/openmatch-backendapi:dev",
|
||||
"imagePullPolicy":"Always",
|
||||
"ports": [
|
||||
{
|
||||
"name": "grpc",
|
||||
"containerPort": 50505
|
||||
},
|
||||
{
|
||||
"name": "metrics",
|
||||
"containerPort": 9555
|
||||
}
|
||||
],
|
||||
"resources":{
|
||||
"requests":{
|
||||
"memory":"100Mi",
|
||||
"cpu":"100m"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-backendapi"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "backend"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 50505,
|
||||
"targetPort": "grpc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"apiVersion":"extensions/v1beta1",
|
||||
"kind":"Deployment",
|
||||
"metadata":{
|
||||
"name":"om-frontendapi",
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "frontend"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"replicas":1,
|
||||
"selector":{
|
||||
"matchLabels":{
|
||||
"app":"openmatch",
|
||||
"component": "frontend"
|
||||
}
|
||||
},
|
||||
"template":{
|
||||
"metadata":{
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "frontend"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"containers":[
|
||||
{
|
||||
"name":"om-frontendapi",
|
||||
"image":"gcr.io/open-match-public-images/openmatch-frontendapi:dev",
|
||||
"imagePullPolicy":"Always",
|
||||
"ports": [
|
||||
{
|
||||
"name": "grpc",
|
||||
"containerPort": 50504
|
||||
},
|
||||
{
|
||||
"name": "metrics",
|
||||
"containerPort": 9555
|
||||
}
|
||||
],
|
||||
"resources":{
|
||||
"requests":{
|
||||
"memory":"100Mi",
|
||||
"cpu":"100m"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-frontendapi"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "frontend"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 50504,
|
||||
"targetPort": "grpc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"apiVersion": "monitoring.coreos.com/v1",
|
||||
"kind": "ServiceMonitor",
|
||||
"metadata": {
|
||||
"name": "openmatch-metrics",
|
||||
"labels": {
|
||||
"app": "openmatch",
|
||||
"agent": "opencensus",
|
||||
"destination": "prometheus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "openmatch",
|
||||
"agent": "opencensus",
|
||||
"destination": "prometheus"
|
||||
}
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"port": "metrics",
|
||||
"interval": "10s"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-frontend-metrics",
|
||||
"labels": {
|
||||
"app": "openmatch",
|
||||
"component": "frontend",
|
||||
"agent": "opencensus",
|
||||
"destination": "prometheus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "frontend"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"name": "metrics",
|
||||
"targetPort": 9555,
|
||||
"port": 19555
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-backend-metrics",
|
||||
"labels": {
|
||||
"app": "openmatch",
|
||||
"component": "backend",
|
||||
"agent": "opencensus",
|
||||
"destination": "prometheus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "backend"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"name": "metrics",
|
||||
"targetPort": 9555,
|
||||
"port": 29555
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-mmforc-metrics",
|
||||
"labels": {
|
||||
"app": "openmatch",
|
||||
"component": "mmforc",
|
||||
"agent": "opencensus",
|
||||
"destination": "prometheus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "mmforc"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"name": "metrics",
|
||||
"targetPort": 9555,
|
||||
"port": 39555
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
{
|
||||
"apiVersion":"extensions/v1beta1",
|
||||
"kind":"Deployment",
|
||||
"metadata":{
|
||||
"name":"om-mmforc",
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmforc"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"replicas":1,
|
||||
"selector":{
|
||||
"matchLabels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmforc"
|
||||
}
|
||||
},
|
||||
"template":{
|
||||
"metadata":{
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmforc"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"containers":[
|
||||
{
|
||||
"name":"om-mmforc",
|
||||
"image":"gcr.io/open-match-public-images/openmatch-mmforc:dev",
|
||||
"imagePullPolicy":"Always",
|
||||
"ports": [
|
||||
{
|
||||
"name": "metrics",
|
||||
"containerPort":9555
|
||||
}
|
||||
],
|
||||
"resources":{
|
||||
"requests":{
|
||||
"memory":"100Mi",
|
||||
"cpu":"100m"
|
||||
}
|
||||
},
|
||||
"env":[
|
||||
{
|
||||
"name":"METADATA_NAMESPACE",
|
||||
"valueFrom": {
|
||||
"fieldRef": {
|
||||
"fieldPath": "metadata.namespace"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": {
|
||||
"name": "mmf-sa"
|
||||
},
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "default",
|
||||
"namespace": "default"
|
||||
}
|
||||
],
|
||||
"roleRef": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "cluster-admin",
|
||||
"apiGroup": "rbac.authorization.k8s.io"
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"apiVersion":"extensions/v1beta1",
|
||||
"kind":"Deployment",
|
||||
"metadata":{
|
||||
"name":"om-mmlogicapi",
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"replicas":1,
|
||||
"selector":{
|
||||
"matchLabels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"template":{
|
||||
"metadata":{
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"containers":[
|
||||
{
|
||||
"name":"om-mmlogic",
|
||||
"image":"gcr.io/open-match-public-images/openmatch-mmlogicapi:dev",
|
||||
"imagePullPolicy":"Always",
|
||||
"ports": [
|
||||
{
|
||||
"name": "grpc",
|
||||
"containerPort": 50503
|
||||
},
|
||||
{
|
||||
"name": "metrics",
|
||||
"containerPort": 9555
|
||||
}
|
||||
],
|
||||
"resources":{
|
||||
"requests":{
|
||||
"memory":"100Mi",
|
||||
"cpu":"100m"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-mmlogicapi"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 50503,
|
||||
"targetPort": "grpc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"apiVersion": "monitoring.coreos.com/v1",
|
||||
"kind": "Prometheus",
|
||||
"metadata": {
|
||||
"name": "prometheus"
|
||||
},
|
||||
"spec": {
|
||||
"serviceMonitorSelector": {
|
||||
"matchLabels": {
|
||||
"app": "openmatch"
|
||||
}
|
||||
},
|
||||
"serviceAccountName": "prometheus",
|
||||
"resources": {
|
||||
"requests": {
|
||||
"memory": "400Mi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": {
|
||||
"name": "prometheus-operator"
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "prometheus-operator"
|
||||
},
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "prometheus-operator",
|
||||
"namespace": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": {
|
||||
"name": "prometheus"
|
||||
}
|
||||
}
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "ClusterRole",
|
||||
"metadata": {
|
||||
"name": "prometheus"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"nodes",
|
||||
"services",
|
||||
"endpoints",
|
||||
"pods"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"configmaps"
|
||||
],
|
||||
"verbs": [
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"nonResourceURLs": [
|
||||
"/metrics"
|
||||
],
|
||||
"verbs": [
|
||||
"get"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": {
|
||||
"name": "prometheus"
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "prometheus"
|
||||
},
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "prometheus",
|
||||
"namespace": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "ClusterRole",
|
||||
"metadata": {
|
||||
"name": "prometheus-operator"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"extensions"
|
||||
],
|
||||
"resources": [
|
||||
"thirdpartyresources"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"apiextensions.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"customresourcedefinitions"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"monitoring.coreos.com"
|
||||
],
|
||||
"resources": [
|
||||
"alertmanagers",
|
||||
"prometheuses",
|
||||
"prometheuses/finalizers",
|
||||
"servicemonitors"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"apps"
|
||||
],
|
||||
"resources": [
|
||||
"statefulsets"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"configmaps",
|
||||
"secrets"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"pods"
|
||||
],
|
||||
"verbs": [
|
||||
"list",
|
||||
"delete"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"services",
|
||||
"endpoints"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"create",
|
||||
"update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"nodes"
|
||||
],
|
||||
"verbs": [
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"namespaces"
|
||||
],
|
||||
"verbs": [
|
||||
"list"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": {
|
||||
"name": "prometheus-operator"
|
||||
}
|
||||
}
|
||||
{
|
||||
"apiVersion": "extensions/v1beta1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"k8s-app": "prometheus-operator"
|
||||
},
|
||||
"name": "prometheus-operator"
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 1,
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"k8s-app": "prometheus-operator"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"args": [
|
||||
"--kubelet-service=kube-system/kubelet",
|
||||
"--config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1"
|
||||
],
|
||||
"image": "quay.io/coreos/prometheus-operator:v0.17.0",
|
||||
"name": "prometheus-operator",
|
||||
"ports": [
|
||||
{
|
||||
"containerPort": 8080,
|
||||
"name": "http"
|
||||
}
|
||||
],
|
||||
"resources": {
|
||||
"limits": {
|
||||
"cpu": "200m",
|
||||
"memory": "100Mi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "50Mi"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 65534
|
||||
},
|
||||
"serviceAccountName": "prometheus-operator"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"name": "prometheus"
|
||||
},
|
||||
"spec": {
|
||||
"type": "NodePort",
|
||||
"ports": [
|
||||
{
|
||||
"name": "web",
|
||||
"nodePort": 30900,
|
||||
"port": 9090,
|
||||
"protocol": "TCP",
|
||||
"targetPort": "web"
|
||||
}
|
||||
],
|
||||
"selector": {
|
||||
"prometheus": "prometheus"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
{
|
||||
"apiVersion": "extensions/v1beta1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "redis-master"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "mm",
|
||||
"tier": "storage"
|
||||
}
|
||||
},
|
||||
"replicas": 1,
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "mm",
|
||||
"tier": "storage"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "redis-master",
|
||||
"image": "redis:4.0.11",
|
||||
"ports": [
|
||||
{
|
||||
"name": "redis",
|
||||
"containerPort": 6379
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "redis"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "mm",
|
||||
"tier": "storage"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 6379,
|
||||
"targetPort": "redis"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
16
doc.go
Normal file
16
doc.go
Normal file
@ -0,0 +1,16 @@
|
||||
// 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 openmatch provides flexible, extensible, and scalable video game matchmaking.
|
||||
package openmatch // import "open-match.dev/open-match"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user