diff --git a/.travis.yml b/.travis.yml index 6fe79b6ab..58e1c8359 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ dist: trusty group: edge language: python python: - - 2.7 + - "2.7" +services: + - docker env: global: - secure: "TNhj8oXrtBzCkkJDA8qbDmqUggK9Kbsy8Itgi0+VmXB+3bVRornZMS38ppaFz+BVOTdL80ZvN2s/OPV106QjFv0Hx1MmWAw4kNST2QBtxFXBHRKYtr7NtBN0jr11el1fG83YdpeZYQbc5aqbJ4OPz2GpfhGfDVhVGPjMFMKXI5XbTbbl+HCEL67ywozt964LhpuuXTaX7jgYFiJUtcwkYRUaDYY3ryJvMSOx95AjKyRMNC5JlgAqbJuYsOTm1eVZtfQ1jYVvd/NuAHOMDNpZWvcIaxTuc3k4XZh4UPryuRJWfjgjIq6kua7Q6ho6W2GbDgN2b/9lIldkTR8QfLSnCNLJIg6KJZ2gmIQg7u+nZHemdugo9XkvfmKXfB/t3HChFX1HNtS4gSeIn874IynLHx3UJ1lxm7BdDbF4Jjijffj5uWDGqVj3/Myd2jdFTJCoLJXvYI7la6ouMzXW5aDFhy2UXK2A3q7aBbaD64+U1R7YPGIyvfAd7NCF11vtRvJGI/fNjO5S1EuSacrQm7CiXu0rd0L5EOSU85XNTQsWN6xxJKEcc8Hx9YLRXkmR7gK9LoEPTUwFbfVXBUvnOsZav3MOBBxzj4+eLxkx2B1vbY2Lx5yPAqyWwi3vet46NEZUIKgqK+xRYQKj6dj3OF1gx7LOcyhpyevdpTZotiEx0C4=" # SNAP_API_AUTH @@ -34,7 +36,6 @@ jobs: before_install: - nvm install node - nvm use node - - gem install sass --version 3.3.4 install: - pip install . - pip install coveralls @@ -44,7 +45,37 @@ jobs: - node djangoBundler.js - popd script: - - python all_tests.py --coverage + - python all_tests.py --no-docker-container-tests --coverage + after_success: + - coveralls + - name: "aimmo-game" + before_install: + - cd aimmo-game + - docker build -t ocadotechnology/aimmo-game:test . + install: + - pip install coveralls + script: + - docker run -it -p 5000:5000 ocadotechnology/aimmo-game:test + after_success: + - coveralls + - name: "aimmo-game-creator" + before_install: + - cd aimmo-game-creator + - docker build -t ocadotechnology/aimmo-game-creator:test . + install: + - pip install coveralls + script: + - docker run -it -p 5000:5000 ocadotechnology/aimmo-game-creator:test + after_success: + - coveralls + - name: "aimmo-game-worker" + before_install: + - cd aimmo-game-worker + - docker build -t ocadotechnology/aimmo-game-worker:test . + install: + - pip install coveralls + script: + - docker run -it -p 5000:5000 ocadotechnology/aimmo-game-worker:test after_success: - coveralls - name: "Javascript Tests" diff --git a/Pipfile b/Pipfile index e97295d39..510baa5fd 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ pylint = "<2.0.0" [packages] "e1839a8" = {editable = true, path = "."} kubernetes = "*" +eventlet = "*" [requires] python_version = "2.7" diff --git a/Pipfile.lock b/Pipfile.lock index 594fb9aec..cc7a7fe3a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1601cf716cf029c4229862e2c9ad929734561dac51e2550e11b18c218643bb8f" + "sha256": "6827d4b02953c498d80756b8070e68979b7108f2b6f388a66b83b003d06804a2" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,10 @@ "default": { "adal": { "hashes": [ - "sha256:534ab04df7ab7c30bc7fe9526c3120e50b9496982f6c85001b05fd7cf4134eb7", - "sha256:a2a2f7e4a2d2e2014e3d5ff9f6d614af280c879a1dbf96bb64d92d85a814a645" + "sha256:ba52913c38d76b4a4d88eaab41a5763d056ab6d073f106e0605b051ab930f5c1", + "sha256:bf79392b8e9e5e82aa6acac3835ba58bbac0ccf7e15befa215863f83d5f6a007" ], - "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", - "version": "==1.1.0" + "version": "==1.2.0" }, "asn1crypto": { "hashes": [ @@ -40,10 +39,10 @@ }, "cachetools": { "hashes": [ - "sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3", - "sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35" + "sha256:0a258d82933a1dd18cb540aca4ac5d5690731e24d1239a08577b814998f49785", + "sha256:4621965b0d9d4c82a79a29edbad19946f5e7702df4afae7d1ed2df951559a8cc" ], - "version": "==2.1.0" + "version": "==3.0.0" }, "certifi": { "hashes": [ @@ -87,7 +86,6 @@ "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" ], - "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", "version": "==1.11.5" }, "chardet": { @@ -102,33 +100,31 @@ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", "version": "==7.0" }, "cryptography": { "hashes": [ - "sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb", - "sha256:10b48e848e1edb93c1d3b797c83c72b4c387ab0eb4330aaa26da8049a6cbede0", - "sha256:17db09db9d7c5de130023657be42689d1a5f60502a14f6f745f6f65a6b8195c0", - "sha256:227da3a896df1106b1a69b1e319dce218fa04395e8cc78be7e31ca94c21254bc", - "sha256:2cbaa03ac677db6c821dac3f4cdfd1461a32d0615847eedbb0df54bb7802e1f7", - "sha256:31db8febfc768e4b4bd826750a70c79c99ea423f4697d1dab764eb9f9f849519", - "sha256:4a510d268e55e2e067715d728e4ca6cd26a8e9f1f3d174faf88e6f2cb6b6c395", - "sha256:6a88d9004310a198c474d8a822ee96a6dd6c01efe66facdf17cb692512ae5bc0", - "sha256:76936ec70a9b72eb8c58314c38c55a0336a2b36de0c7ee8fb874a4547cadbd39", - "sha256:7e3b4aecc4040928efa8a7cdaf074e868af32c58ffc9bb77e7bf2c1a16783286", - "sha256:8168bcb08403ef144ff1fb880d416f49e2728101d02aaadfe9645883222c0aa5", - "sha256:8229ceb79a1792823d87779959184a1bf95768e9248c93ae9f97c7a2f60376a1", - "sha256:8a19e9f2fe69f6a44a5c156968d9fc8df56d09798d0c6a34ccc373bb186cee86", - "sha256:8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6", - "sha256:be495b8ec5a939a7605274b6e59fbc35e76f5ad814ae010eb679529671c9e119", - "sha256:dc2d3f3b1548f4d11786616cf0f4415e25b0fbecb8a1d2cd8c07568f13fdde38", - "sha256:e4aecdd9d5a3d06c337894c9a6e2961898d3f64fe54ca920a72234a3de0f9cb3", - "sha256:e79ab4485b99eacb2166f3212218dd858258f374855e1568f728462b0e6ee0d9", - "sha256:f995d3667301e1754c57b04e0bae6f0fa9d710697a9f8d6712e8cca02550910f" - ], - "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", - "version": "==2.3.1" + "sha256:02915ee546b42ce513e8167140e9937fc4c81a06a82216e086ccce51f347948a", + "sha256:03cc8bc5a69ae3d44acf1a03facdb7c10a94c67907862c563e10efe72b737977", + "sha256:07f76bde6815c55195f3b3812d35769cc7c765144c0bb71ae45e02535d078591", + "sha256:13eac1c477b9af7e9a9024369468d08aead6ad78ed599d163ad046684474364b", + "sha256:179bfb585c5efc87ae0e665770e4896727b92dbc1f810c761b1ebf8363e2fec8", + "sha256:414af0ba308e74c1f8bc5b11befc86cb66b10be8959547786f64258830d2096f", + "sha256:41a1ca14f255df8c44dd22c6006441d631d1589104045ec7263cc47e9772f41a", + "sha256:54947eb98bc4eef99ddf49f45d2694ea5a3929ab3edc9806ad01967368594d82", + "sha256:5bac7a2abda07d0c3c8429210349bb54149ad8940dc7bcffedcd56519b410a3c", + "sha256:7f41af8c586bed9f59cfe8832d818b3b75c860d7025da9cd2db76875a72ff785", + "sha256:8004fae1b3cb2dbd90a011ad972e49a7e78a871b89c70cc7213cf4ebd2532bcb", + "sha256:8e0eccadc3b465e12c50a5b8fb4d39cf401b44d7bb9936c70fddb5e5aaf740d5", + "sha256:95b4741722269cfdc134fec23b7ae6503ee2aea83d0924cfee6d6ec54cd42d8e", + "sha256:a06f5aa6d7a94531dfe82eb2972e669258c452fe9cf88f76116610de4c789785", + "sha256:b0833d27c7eb536bc27323a1e8e22cb39ebac78c4ef3be0167ba40f447344808", + "sha256:b72dec675bc59a01edc96616cd48ec465b714481caa0938c8bbca5d18f17d5df", + "sha256:c800ddc23b5206ce025f23225fdde89cdc0e64016ad914d5be32d1f602ce9495", + "sha256:c980c8c313a5e014ae12e2245e89e7b30427e5a98cbb88afe478ecae85f3abaa", + "sha256:e85b410885addaeb31a867eabcefc9ef4a7e904ad45eac9e60a763a54b244626" + ], + "version": "==2.4.1" }, "django": { "hashes": [ @@ -177,8 +173,7 @@ "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" ], - "markers": "python_version < '3.4'", - + "markers": "python_version == '2.7'", "version": "==1.1.6" }, "eventlet": { @@ -186,6 +181,7 @@ "sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8", "sha256:d9d31a3c8dbcedbcce5859a919956d934685b17323fc80e1077cb344a2ffa68d" ], + "index": "pypi", "version": "==0.24.1" }, "flask": { @@ -197,10 +193,10 @@ }, "flask-cors": { "hashes": [ - "sha256:e4c8fc15d3e4b4cce6d3b325f2bab91e0e09811a61f50d7a53493bc44242a4f1", - "sha256:ecc016c5b32fa5da813ec8d272941cfddf5f6bba9060c405a70285415cbf24c9" + "sha256:7ad56ee3b90d4955148fc25a2ecaa1124fc84298471e266a7fea59aeac4405a5", + "sha256:7e90bf225fdf163d11b84b59fb17594d0580a16b97ab4e1146b1fb2737c1cfec" ], - "version": "==3.0.6" + "version": "==3.0.7" }, "flask-socketio": { "hashes": [ @@ -211,10 +207,10 @@ }, "google-auth": { "hashes": [ - "sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530", - "sha256:a4cf9e803f2176b5de442763bd339b313d3f1ed3002e3e1eb6eec1d7c9bbc9b4" + "sha256:494e747bdc2cdeb0fa6ef85118de2ea1a563f160294cce05048c6ff563fda1bb", + "sha256:b08a27888e9d1c17a891b3688aacc9c6f2019d7f6c5a2e73588e6bb9a2c0fa98" ], - "version": "==1.5.1" + "version": "==1.6.1" }, "greenlet": { "hashes": [ @@ -242,11 +238,11 @@ }, "hypothesis": { "hashes": [ - "sha256:a5290a76d6ecbcd621566fde9677c86ad6881b02af973a401310dd2e3b0f345c", - "sha256:eb0ea8265c3bf9c4552b2b6de805056104f4b01c2af59c1b3febe41dddabfcdb", - "sha256:f9f1515858d904fe1d473fb5099747dd7c5f25392f2d3593c6b4b68e595b1953" + "sha256:43cabc87f32adc90fec41cb3f36d96c2e5d564d71f2e60817cd2b23d24c7ad7a", + "sha256:46eef013dafe09467bc83065ab621b51098a84712ab39399fd15477406807c13", + "sha256:f33d2e50080e09dcd4e81f939b1d825c6f5b104d89ebad7f57cdd3ea11983680" ], - "version": "==3.77.0" + "version": "==3.82.1" }, "idna": { "hashes": [ @@ -265,9 +261,10 @@ }, "itsdangerous": { "hashes": [ - "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], - "version": "==0.24" + "version": "==1.1.0" }, "jinja2": { "hashes": [ @@ -278,17 +275,44 @@ }, "kubernetes": { "hashes": [ - "sha256:5ee6e2e949ca800ad8a73da6f67c2a637c2c803945b006e6105beae83e43b273", - "sha256:84dfb4319afac189e8327b71b9332b5329d2a78074f58958c5f06a870edf32ba" + "sha256:0cc9ce02d838da660efa0a67270b4b7d47e6beb8889673cd45c86f897e2d6821", + "sha256:54f8e7bb1dd9a55cf416dff76a63c4ae441764280942d9913f2243676f29d02c" ], "index": "pypi", - "version": "==7.0.0" + "version": "==8.0.0" }, "markupsafe": { "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" ], - "version": "==1.0" + "version": "==1.1.0" }, "monotonic": { "hashes": [ @@ -304,27 +328,19 @@ ], "version": "==2.1.0" }, - "prometheus-client": { - "hashes": [ - "sha256:046cb4fffe75e55ff0e6dfd18e2ea16e54d86cc330f369bebcc683475c8b68a9" - ], - "index": "pypi", - "version": "==0.4.2" - }, "psutil": { "hashes": [ - "sha256:0d8da7333549a998556c18eb2af3ce902c28d66ceb947505c008f91e9f988abd", - "sha256:1914bacbd2fc2af8f795daa44b9d2e0649a147460cfd21b1a70a124472f66d40", - "sha256:215d61a901e67b1a35e14c6aedef317f7fa7e6075a20c150fd11bd2c906d2c83", - "sha256:51057c03aea251ad6667c2bba259bc7ed3210222d3a74152c84e3ab06e1da0ba", - "sha256:5b6322b167a5ba0c5463b4d30dfd379cd4ce245a1162ebf8fc7ab5c5ffae4f3b", - "sha256:a890c3e490493f21da2817ffc92822693bc0d6bcac9999caa04ffce8dd4e7132", - "sha256:b34611280a2d0697f1c499e15e936d88109170194b390599c98bab8072a71f05", - "sha256:cea2557ee6a9faa2c100947637ded68414e12b851633c4ce26e0311b2a2ed539", - "sha256:d081707ef0081920533db30200a2d30d5c0ea9cf6afa7cf8881ae4516cc69c48" + "sha256:1c19957883e0b93d081d41687089ad630e370e26dc49fd9df6951d6c891c4736", + "sha256:1c71b9716790e202a00ab0931a6d1e25db1aa1198bcacaea2f5329f75d257fff", + "sha256:3b7a4daf4223dae171a67a89314ac5ca0738e94064a78d99cfd751c55d05f315", + "sha256:3e19be3441134445347af3767fa7770137d472a484070840eee6653b94ac5576", + "sha256:6e265c8f3da00b015d24b842bfeb111f856b13d24f2c57036582568dc650d6c3", + "sha256:809c9cef0402e3e48b5a1dddc390a8a6ff58b15362ea5714494073fa46c3d293", + "sha256:b4d1b735bf5b120813f4c89db8ac22d89162c558cbd7fdd298866125fe906219", + "sha256:bbffac64cfd01c6bcf90eb1bedc6c80501c4dae8aef4ad6d6dd49f8f05f6fc5a", + "sha256:bfcea4f189177b2d2ce4a34b03c4ac32c5b4c22e21f5b093d9d315e6e253cd81" ], - "markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version != '3.2.*'", - "version": "==5.4.7" + "version": "==5.4.8" }, "pyasn1": { "hashes": [ @@ -344,7 +360,6 @@ "hashes": [ "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], - "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", "version": "==2.19" }, "pyjwt": { @@ -356,10 +371,10 @@ }, "python-dateutil": { "hashes": [ - "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", - "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", + "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" ], - "version": "==2.7.3" + "version": "==2.7.5" }, "python-engineio": { "hashes": [ @@ -375,13 +390,6 @@ ], "version": "==2.0.0" }, - "pytz": { - "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" - ], - "version": "==2018.5" - }, "pyyaml": { "hashes": [ "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", @@ -400,10 +408,10 @@ }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], - "version": "==2.19.1" + "version": "==2.20.1" }, "requests-oauthlib": { "hashes": [ @@ -428,25 +436,17 @@ }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", - "version": "==1.23" - }, - "uwsgi": { - "hashes": [ - "sha256:d2318235c74665a60021a4fc7770e9c2756f9fc07de7b8c22805efe85b5ab277" - ], - "index": "pypi", - "version": "==2.0.17.1" + "version": "==1.24.1" }, "websocket-client": { "hashes": [ - "sha256:c42b71b68f9ef151433d6dcc6a7cb98ac72d2ad1e3a74981ca22bc5d9134f166", - "sha256:f5889b1d0a994258cfcbc8f2dc3e457f6fc7b32a8d74873033d12e4eab4bdf63" + "sha256:8c8bf2d4f800c3ed952df206b18c28f7070d9e3dcbd6ca6291127574f57ee786", + "sha256:e51562c91ddb8148e791f0155fdb01325d99bb52c4cdbb291aee7a3563fd0849" ], - "version": "==0.53.0" + "version": "==0.54.0" }, "werkzeug": { "hashes": [ @@ -462,7 +462,6 @@ "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a", "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a" ], - "markers": "python_version >= '2.6'", "version": "==1.6.5" }, "backports.functools-lru-cache": { @@ -470,7 +469,7 @@ "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd" ], - "markers": "python_version < '3.4'", + "markers": "python_version == '2.7'", "version": "==1.5" }, "configparser": { @@ -487,7 +486,7 @@ "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" ], - "markers": "python_version < '3.4'", + "markers": "python_version == '2.7'", "version": "==1.1.6" }, "futures": { @@ -495,7 +494,6 @@ "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265", "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1" ], - "markers": "python_version < '3' and python_version >= '2.6'", "version": "==3.2.0" }, "isort": { @@ -504,7 +502,6 @@ "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" ], - "markers": "python_version < '3' and python_version >= '2.6'", "version": "==4.3.4" }, "lazy-object-proxy": { diff --git a/aimmo-game-creator/Dockerfile b/aimmo-game-creator/Dockerfile index 0117cf090..135413cda 100644 --- a/aimmo-game-creator/Dockerfile +++ b/aimmo-game-creator/Dockerfile @@ -1,15 +1,28 @@ -FROM python:2-alpine - +FROM python:3.6 as builder MAINTAINER code@ocado.com - -RUN apk add --no-cache gcc musl-dev python-dev libffi-dev openssl-dev - +# RUN apk add --no-cache gcc musl-dev python-dev libffi-dev openssl-dev RUN pip install pipenv - COPY ["Pipfile", "Pipfile.lock", "setup.py", "./"] - +RUN pipenv install coverage RUN pipenv install --system --deploy +FROM python:3.6-alpine as base +COPY --from=builder /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages COPY . . -CMD ["python", "./service.py"] + +FROM base as runner +ENV WORKER_MANAGER=kubernetes +CMD ["python", "./service.py", "0.0.0.0"] + + +FROM base as tester +ENV WORKER_MANAGER=kubernetes +CMD ["python", "setup.py", "test"] + +FROM base as coverage_tester +ENV WORKER_MANAGER=kubernetes +ENV WITH_COVERAGE='True' +RUN apk add bash +COPY --from=builder /usr/local/bin/coverage /usr/local/bin/coverage +CMD python setup.py test \ No newline at end of file diff --git a/aimmo-game-creator/Pipfile b/aimmo-game-creator/Pipfile index 99ce8d0a6..6da8ef065 100644 --- a/aimmo-game-creator/Pipfile +++ b/aimmo-game-creator/Pipfile @@ -7,6 +7,7 @@ name = "pypi" kubernetes = "*" aimmo-game-creator = {editable = true, path = "."} docker = "*" +coverage = "*" [requires] -python_version = "2.7" +python_version = "3.6.7" diff --git a/aimmo-game-creator/Pipfile.lock b/aimmo-game-creator/Pipfile.lock index dea398248..ed6b9fcf5 100644 --- a/aimmo-game-creator/Pipfile.lock +++ b/aimmo-game-creator/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "055a0f8b0f2d76d025788afd11d00a5e36ac7fc3539c01a3b7d7e1831293d033" + "sha256": "0a9a9775ea69c8dd572b73cad7cdb6f7460a87bd98956a74b0cbe70d800a98ae" }, "pipfile-spec": 6, "requires": { - "python_version": "2.7" + "python_version": "3.6.7" }, "sources": [ { @@ -18,10 +18,10 @@ "default": { "adal": { "hashes": [ - "sha256:534ab04df7ab7c30bc7fe9526c3120e50b9496982f6c85001b05fd7cf4134eb7", - "sha256:a2a2f7e4a2d2e2014e3d5ff9f6d614af280c879a1dbf96bb64d92d85a814a645" + "sha256:ba52913c38d76b4a4d88eaab41a5763d056ab6d073f106e0605b051ab930f5c1", + "sha256:bf79392b8e9e5e82aa6acac3835ba58bbac0ccf7e15befa215863f83d5f6a007" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "aimmo-game-creator": { "editable": true, @@ -34,26 +34,19 @@ ], "version": "==0.24.0" }, - "backports.ssl-match-hostname": { - "hashes": [ - "sha256:502ad98707319f4a51fa2ca1c677bd659008d27ded9f6380c79e8932e38dcdf2" - ], - "markers": "python_version < '3.5'", - "version": "==3.5.0.1" - }, "cachetools": { "hashes": [ - "sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3", - "sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35" + "sha256:0a258d82933a1dd18cb540aca4ac5d5690731e24d1239a08577b814998f49785", + "sha256:4621965b0d9d4c82a79a29edbad19946f5e7702df4afae7d1ed2df951559a8cc" ], - "version": "==2.1.0" + "version": "==3.0.0" }, "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.8.24" + "version": "==2018.10.15" }, "cffi": { "hashes": [ @@ -99,29 +92,66 @@ ], "version": "==3.0.4" }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, "cryptography": { "hashes": [ - "sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb", - "sha256:10b48e848e1edb93c1d3b797c83c72b4c387ab0eb4330aaa26da8049a6cbede0", - "sha256:17db09db9d7c5de130023657be42689d1a5f60502a14f6f745f6f65a6b8195c0", - "sha256:227da3a896df1106b1a69b1e319dce218fa04395e8cc78be7e31ca94c21254bc", - "sha256:2cbaa03ac677db6c821dac3f4cdfd1461a32d0615847eedbb0df54bb7802e1f7", - "sha256:31db8febfc768e4b4bd826750a70c79c99ea423f4697d1dab764eb9f9f849519", - "sha256:4a510d268e55e2e067715d728e4ca6cd26a8e9f1f3d174faf88e6f2cb6b6c395", - "sha256:6a88d9004310a198c474d8a822ee96a6dd6c01efe66facdf17cb692512ae5bc0", - "sha256:76936ec70a9b72eb8c58314c38c55a0336a2b36de0c7ee8fb874a4547cadbd39", - "sha256:7e3b4aecc4040928efa8a7cdaf074e868af32c58ffc9bb77e7bf2c1a16783286", - "sha256:8168bcb08403ef144ff1fb880d416f49e2728101d02aaadfe9645883222c0aa5", - "sha256:8229ceb79a1792823d87779959184a1bf95768e9248c93ae9f97c7a2f60376a1", - "sha256:8a19e9f2fe69f6a44a5c156968d9fc8df56d09798d0c6a34ccc373bb186cee86", - "sha256:8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6", - "sha256:be495b8ec5a939a7605274b6e59fbc35e76f5ad814ae010eb679529671c9e119", - "sha256:dc2d3f3b1548f4d11786616cf0f4415e25b0fbecb8a1d2cd8c07568f13fdde38", - "sha256:e4aecdd9d5a3d06c337894c9a6e2961898d3f64fe54ca920a72234a3de0f9cb3", - "sha256:e79ab4485b99eacb2166f3212218dd858258f374855e1568f728462b0e6ee0d9", - "sha256:f995d3667301e1754c57b04e0bae6f0fa9d710697a9f8d6712e8cca02550910f" - ], - "version": "==2.3.1" + "sha256:02915ee546b42ce513e8167140e9937fc4c81a06a82216e086ccce51f347948a", + "sha256:03cc8bc5a69ae3d44acf1a03facdb7c10a94c67907862c563e10efe72b737977", + "sha256:07f76bde6815c55195f3b3812d35769cc7c765144c0bb71ae45e02535d078591", + "sha256:13eac1c477b9af7e9a9024369468d08aead6ad78ed599d163ad046684474364b", + "sha256:179bfb585c5efc87ae0e665770e4896727b92dbc1f810c761b1ebf8363e2fec8", + "sha256:414af0ba308e74c1f8bc5b11befc86cb66b10be8959547786f64258830d2096f", + "sha256:41a1ca14f255df8c44dd22c6006441d631d1589104045ec7263cc47e9772f41a", + "sha256:54947eb98bc4eef99ddf49f45d2694ea5a3929ab3edc9806ad01967368594d82", + "sha256:5bac7a2abda07d0c3c8429210349bb54149ad8940dc7bcffedcd56519b410a3c", + "sha256:7f41af8c586bed9f59cfe8832d818b3b75c860d7025da9cd2db76875a72ff785", + "sha256:8004fae1b3cb2dbd90a011ad972e49a7e78a871b89c70cc7213cf4ebd2532bcb", + "sha256:8e0eccadc3b465e12c50a5b8fb4d39cf401b44d7bb9936c70fddb5e5aaf740d5", + "sha256:95b4741722269cfdc134fec23b7ae6503ee2aea83d0924cfee6d6ec54cd42d8e", + "sha256:a06f5aa6d7a94531dfe82eb2972e669258c452fe9cf88f76116610de4c789785", + "sha256:b0833d27c7eb536bc27323a1e8e22cb39ebac78c4ef3be0167ba40f447344808", + "sha256:b72dec675bc59a01edc96616cd48ec465b714481caa0938c8bbca5d18f17d5df", + "sha256:c800ddc23b5206ce025f23225fdde89cdc0e64016ad914d5be32d1f602ce9495", + "sha256:c980c8c313a5e014ae12e2245e89e7b30427e5a98cbb88afe478ecae85f3abaa", + "sha256:e85b410885addaeb31a867eabcefc9ef4a7e904ad45eac9e60a763a54b244626" + ], + "version": "==2.4.1" }, "dnspython": { "hashes": [ @@ -132,11 +162,11 @@ }, "docker": { "hashes": [ - "sha256:6c4da20ef40e8d3eaf650f1488d91452b9a1128045481d7169fd34665ffa90ee", - "sha256:bc693be5a84b3b9e5aaf156068c5c0a445ee5138c638c3fbc857133bf67ebe07" + "sha256:31421f16c01ffbd1ea7353c7e7cd7540bf2e5906d6173eb51c8fea4e0ea38b19", + "sha256:fbe82af9b94ccced752527c8de07fa20267f9634b48674ba478a0bb4000a0b1e" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.5.1" }, "docker-pycreds": { "hashes": [ @@ -145,16 +175,6 @@ ], "version": "==0.3.0" }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "markers": "python_version < '3'", - "version": "==1.1.6" - }, "eventlet": { "hashes": [ "sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8", @@ -164,10 +184,10 @@ }, "google-auth": { "hashes": [ - "sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530", - "sha256:a4cf9e803f2176b5de442763bd339b313d3f1ed3002e3e1eb6eec1d7c9bbc9b4" + "sha256:494e747bdc2cdeb0fa6ef85118de2ea1a563f160294cce05048c6ff563fda1bb", + "sha256:b08a27888e9d1c17a891b3688aacc9c6f2019d7f6c5a2e73588e6bb9a2c0fa98" ], - "version": "==1.5.1" + "version": "==1.6.1" }, "greenlet": { "hashes": [ @@ -200,21 +220,13 @@ ], "version": "==2.7" }, - "ipaddress": { - "hashes": [ - "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", - "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" - ], - "markers": "python_version == '2.7'", - "version": "==1.0.22" - }, "kubernetes": { "hashes": [ - "sha256:5ee6e2e949ca800ad8a73da6f67c2a637c2c803945b006e6105beae83e43b273", - "sha256:84dfb4319afac189e8327b71b9332b5329d2a78074f58958c5f06a870edf32ba" + "sha256:0cc9ce02d838da660efa0a67270b4b7d47e6beb8889673cd45c86f897e2d6821", + "sha256:54f8e7bb1dd9a55cf416dff76a63c4ae441764280942d9913f2243676f29d02c" ], "index": "pypi", - "version": "==7.0.0" + "version": "==8.0.0" }, "monotonic": { "hashes": [ @@ -232,37 +244,15 @@ }, "pyasn1": { "hashes": [ - "sha256:0ad0fe0593dde1e599cac0bf65bb1a4ec663032f0bc68ee44850db4251e8c501", - "sha256:13794d835643ee970b2c059dbfe4eb5d751e16c693c8baee61c526abd209e5c7", - "sha256:49a8ed515f26913049113820b462f698e6ed26df62c389dafb6fa3685ddca8de", - "sha256:74ac8521a0480f228549be20bea555ae35678f0e754c2fbc6f1576b0959bec43", - "sha256:89399ca8ecd4524f974e926d4ef9e7a787903e01f0a9cdff3131ad1361792fe5", - "sha256:8f291e0338d519a1a0d07f0b9d03c9265f6be26eb32fdd21af6d3259d14ea49c", "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", - "sha256:d3bbd726c1a760d4ca596a4d450c380b81737612fe0182f5bb3caebc17461fd9", - "sha256:dea873d6c907c1cf1341fd88742a61efce33227d7743cb37564ab7d7e77dd9fd", - "sha256:ded5eea5cb88bc1ce9aa074b5a3092f95ce4741887e317e9b49c7ece75d7ea0e", - "sha256:e8b69ea2200d42201cbedd486eedb8980f320d4534f83ce2fb468e96aa5545d0", - "sha256:edad117649643230493aeb4955456ce19ab4b12e94489dde6f7094cdb5a3c87e", "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" ], "version": "==0.4.4" }, "pyasn1-modules": { "hashes": [ - "sha256:077250b34432520430bc1c80dcbda4e354090785567c33ded35faa6df8d24753", - "sha256:0da2f947e8ad2697e86fe5fd0e55a4093a2fd79d839c9e19c34e28097db7002c", - "sha256:35ff894a0b5df8e28b700126b2869c7dcfb2b2db5bc82e5d5e82547069241553", - "sha256:44688b94841349648b1e1a5a7a3d96e6596d5d4f21d0b59a82307e153c4dc74b", - "sha256:833716dde880a7f2f2ccdeea9a096842626981ff2a477d8b318c0906367ac11b", "sha256:a0cf3e1842e7c60fde97cb22d275eb6f9524f5c5250489e292529de841417547", - "sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e", - "sha256:a728bb9502d1fdc104c66f24a176b6a70a32e89d1d8a5b55c959233ed51c67be", - "sha256:c30a098435ea0989c37005a971843e9d3966c7f6d056ddbf052e5061c06e3291", - "sha256:c355a45b32c5bc1d9893eceb704b0cfcd1126f91b5a7b9ee64c1c05383283381", - "sha256:e64679de1940f41ead5170fce364d54e7b9e2e862f064727b6bcb5cee753b7a2", - "sha256:ed71d20225c356881c29f0b1d7a0d6521563a389d9478e8f95d798cc5ba07b88", - "sha256:f183f0940b9f5ed2ad9d04c80cab2451440fa9af4fc959d85113fadd2e777962" + "sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e" ], "version": "==0.2.2" }, @@ -281,10 +271,10 @@ }, "python-dateutil": { "hashes": [ - "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", - "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", + "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" ], - "version": "==2.7.3" + "version": "==2.7.5" }, "pyyaml": { "hashes": [ @@ -304,16 +294,15 @@ }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], - "version": "==2.19.1" + "version": "==2.20.1" }, "requests-oauthlib": { "hashes": [ "sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f", - "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8", - "sha256:fe3282f48fb134ee0035712159f5429215459407f6d5484013343031ff1a400d" + "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8" ], "version": "==1.0.0" }, @@ -333,17 +322,17 @@ }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "version": "==1.23" + "version": "==1.24.1" }, "websocket-client": { "hashes": [ - "sha256:c42b71b68f9ef151433d6dcc6a7cb98ac72d2ad1e3a74981ca22bc5d9134f166", - "sha256:f5889b1d0a994258cfcbc8f2dc3e457f6fc7b32a8d74873033d12e4eab4bdf63" + "sha256:8c8bf2d4f800c3ed952df206b18c28f7070d9e3dcbd6ca6291127574f57ee786", + "sha256:e51562c91ddb8148e791f0155fdb01325d99bb52c4cdbb291aee7a3563fd0849" ], - "version": "==0.53.0" + "version": "==0.54.0" } }, "develop": {} diff --git a/aimmo-game-creator/game_manager.py b/aimmo-game-creator/game_manager.py index 7017df6bb..7cfed698a 100644 --- a/aimmo-game-creator/game_manager.py +++ b/aimmo-game-creator/game_manager.py @@ -5,8 +5,9 @@ from abc import ABCMeta, abstractmethod import requests -from eventlet.greenpool import GreenPool from eventlet.semaphore import Semaphore +from concurrent import futures +from concurrent.futures import ALL_COMPLETED import kubernetes import docker import json @@ -60,23 +61,23 @@ class GameManager(object): def __init__(self, games_url): self._data = _GameManagerData() self.games_url = games_url - self._pool = GreenPool(size=3) super(GameManager, self).__init__() @abstractmethod def create_game(self, game_id, game_data): """Creates a new game""" - raise NotImplemented + raise NotImplementedError @abstractmethod def delete_game(self, game_id): """Deletes the given game""" - raise NotImplemented + raise NotImplementedError - def recreate_game(self, game_id, game_data): + def recreate_game(self, game_to_add): """Deletes and recreates the given game""" + game_id, game_data = game_to_add LOGGER.info("Deleting game {}".format(game_data["name"])) try: self.delete_game(game_id) @@ -104,15 +105,12 @@ def update(self): id: games[id] for id in self._data.add_new_games(games.keys()) } - LOGGER.debug("Need to add games: {}".format(games_to_add)) # Add missing games - self._parallel_map(self.recreate_game, games_to_add.keys(), games_to_add.values()) - + self._parallel_map(self.recreate_game, games_to_add.items()) # Delete extra games known_games = set(games.keys()) removed_game_ids = self._data.remove_unknown_games(known_games) - LOGGER.debug("Removing games: {}".format(removed_game_ids)) self._parallel_map(self.delete_game, removed_game_ids) def get_persistent_state(self, player_id): @@ -126,8 +124,9 @@ def run(self): LOGGER.info("Sleeping") time.sleep(10) - def _parallel_map(self, func, *iterable_args): - list(self._pool.imap(func, *iterable_args)) + def _parallel_map(self, func, iterable_args): + with futures.ThreadPoolExecutor() as executor: + _ = executor.map(func, iterable_args) class LocalGameManager(GameManager): @@ -154,7 +153,6 @@ def setup_container_environment_variables(template, game_data): template['environment']['CONTAINER_TEMPLATE'] = os.environ['CONTAINER_TEMPLATE'] assert (game_id not in self.games) - game_data = {str(k): str(v) for k, v in game_data.items()} port = str(6001 + int(game_id) * 1000) client = docker.from_env() @@ -244,6 +242,7 @@ def _create_game_rc(self, game_id, environment_variables): environment_variables['IMAGE_SUFFIX'] = os.environ.get('IMAGE_SUFFIX', 'latest') environment_variables['K8S_NAMESPACE'] = K8S_NAMESPACE environment_variables['WORKER_MANAGER'] = 'kubernetes' + environment_variables['EXTERNAL_PORT'] = "5000" rc = self._make_rc(environment_variables, game_id) self.api.create_namespaced_replication_controller(K8S_NAMESPACE, rc) diff --git a/aimmo-game-creator/setup.py b/aimmo-game-creator/setup.py index b6b43b22f..dbda8e559 100644 --- a/aimmo-game-creator/setup.py +++ b/aimmo-game-creator/setup.py @@ -1,5 +1,17 @@ # -*- coding: utf-8 -*- from setuptools import find_packages, setup +import coverage +import sys +import os + + +withcoverage = os.environ.get('WITH_COVERAGE') + +if withcoverage == 'True': + print("starting code coverage engine") + coveragedatafile = ".coverage" + cov = coverage.Coverage(config_file=False) + cov.start() setup( @@ -16,3 +28,8 @@ test_suite='tests', zip_safe=False, ) + +if withcoverage == 'True': + print("saving coverage stats") + cov.save() + print("exiting program") diff --git a/aimmo-game-creator/tests/test_game_manager.py b/aimmo-game-creator/tests/test_game_manager.py index 951d3e11a..e545ce70d 100644 --- a/aimmo-game-creator/tests/test_game_manager.py +++ b/aimmo-game-creator/tests/test_game_manager.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import cPickle as pickle import unittest from json import dumps @@ -40,11 +39,11 @@ def _generate_response(self, num_games): return { str(i): { "name": "Game {}".format(i), - "settings": pickle.dumps({ + "settings": { "test": i, "test2": "Settings {}".format(i), - }) - } for i in xrange(num_games) + } + } for i in range(num_games) } def __call__(self, url, request): @@ -61,7 +60,7 @@ def test_correct_url_requested(self): with HTTMock(mocker): self.game_manager.update() self.assertEqual(len(mocker.urls_requested), 1) - self.assertRegexpMatches(mocker.urls_requested[0], "http://test/*") + self.assertRegex(mocker.urls_requested[0], "http://test/*") def test_games_added(self): mocker = RequestMock(3) @@ -69,12 +68,9 @@ def test_games_added(self): self.game_manager.update() self.assertEqual(len(self.game_manager.final_games), 3) self.assertEqual(len(list(self.game_manager._data.get_games())), 3) - for i in xrange(3): + for i in range(3): self.assertIn(str(i), self.game_manager.final_games) - self.assertEqual( - pickle.loads(str(self.game_manager.added_games[str(i)]["settings"])), - {"test": i, "test2": "Settings {}".format(i)} - ) + self.assertEqual(self.game_manager.added_games[str(i)]["settings"], {"test": i, "test2": "Settings {}".format(i)}) self.assertEqual(self.game_manager.added_games[str(i)]["name"], "Game {}".format(i)) def test_remove_games(self): @@ -89,7 +85,7 @@ def test_added_games_given_correct_url(self): mocker = RequestMock(3) with HTTMock(mocker): self.game_manager.update() - for i in xrange(3): + for i in range(3): self.assertEqual( self.game_manager.added_games[str(i)]["GAME_API_URL"], "http://test/{}/".format(i) diff --git a/aimmo-game-worker/Dockerfile b/aimmo-game-worker/Dockerfile index b451f9dd0..b58c307bd 100644 --- a/aimmo-game-worker/Dockerfile +++ b/aimmo-game-worker/Dockerfile @@ -1,15 +1,28 @@ -FROM python:2-alpine - +FROM python:3.6 as builder MAINTAINER code@ocado.com -RUN apk add --no-cache gcc musl-dev python-dev libffi-dev openssl-dev - RUN pip install pipenv - COPY ["Pipfile", "Pipfile.lock", "setup.py", "./"] - +RUN pipenv install coverage RUN pipenv install --system --deploy +FROM python:3.6-alpine as base +COPY --from=builder /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages COPY . . -CMD python service.py 0.0.0.0 $PORT $DATA_URL +FROM base as runner +ENV WORKER_MANAGER=kubernetes +ENV FLASK_ENV='development' +RUN apk add --no-cache bash +CMD python ./service.py 0.0.0.0 $PORT $DATA_URL + +FROM base as tester +ENV WORKER_MANAGER=kubernetes +CMD python setup.py test + +FROM base as coverage_tester +ENV WORKER_MANAGER=kubernetes +ENV WITH_COVERAGE='True' +RUN apk add bash +COPY --from=builder /usr/local/bin/coverage /usr/local/bin/coverage +CMD python setup.py test \ No newline at end of file diff --git a/aimmo-game-worker/Pipfile b/aimmo-game-worker/Pipfile index 3df21592c..592821977 100644 --- a/aimmo-game-worker/Pipfile +++ b/aimmo-game-worker/Pipfile @@ -7,6 +7,7 @@ name = "pypi" kubernetes = "*" aimmo-game-worker = {editable = true, path = "."} restrictedpython = "==4.0.b7" +coverage = "*" [requires] -python_version = "2.7" +python_version = "3.6.7" diff --git a/aimmo-game-worker/Pipfile.lock b/aimmo-game-worker/Pipfile.lock index 467253906..4eb0d38f2 100644 --- a/aimmo-game-worker/Pipfile.lock +++ b/aimmo-game-worker/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "fbae5bb9d29ab27c4476079e4c6d12448eee072cc95784df90c6b69ffd7adfe1" + "sha256": "b0fa8e10b3faf6d4cbca2dd49ae4ecfa632614d239e974fb5d2e08f14fce1f7c" }, "pipfile-spec": 6, "requires": { - "python_version": "2.7" + "python_version": "3.6.7" }, "sources": [ { @@ -99,6 +99,43 @@ ], "version": "==7.0" }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, "cryptography": { "hashes": [ "sha256:02915ee546b42ce513e8167140e9937fc4c81a06a82216e086ccce51f347948a", @@ -123,16 +160,6 @@ ], "version": "==2.4.1" }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "markers": "python_version < '3'", - "version": "==1.1.6" - }, "flask": { "hashes": [ "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", @@ -154,14 +181,6 @@ ], "version": "==2.7" }, - "ipaddress": { - "hashes": [ - "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", - "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" - ], - "markers": "python_version == '2.7'", - "version": "==1.0.22" - }, "itsdangerous": { "hashes": [ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", diff --git a/aimmo-game-worker/avatar_runner.py b/aimmo-game-worker/avatar_runner.py index ef6d25d59..380f1a222 100644 --- a/aimmo-game-worker/avatar_runner.py +++ b/aimmo-game-worker/avatar_runner.py @@ -7,10 +7,7 @@ import inspect import re -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +from io import StringIO import simulation.action as avatar_action import simulation.direction as direction @@ -20,16 +17,10 @@ from RestrictedPython import compile_restricted, utility_builtins from RestrictedPython.Guards import safe_builtins, safer_getattr, guarded_setattr, full_write_guard +from RestrictedPython.PrintCollector import PrintCollector LOGGER = logging.getLogger(__name__) -try: - import __builtin__ -except ImportError: - raise ImportError - # Python 3 - import builtins as __builtin__ - def add_actions_to_globals(): action_classes = filter(lambda x: x[1].__module__ == "simulation.action", inspect.getmembers(avatar_action, inspect.isclass)) @@ -48,7 +39,7 @@ def add_actions_to_globals(): restricted_globals['_getattr_'] = _getattr_ restricted_globals['_setattr_'] = _setattr_ restricted_globals['_getiter_'] = list -restricted_globals['_print_'] = print +restricted_globals['_print_'] = PrintCollector restricted_globals['_write_'] = _write_ restricted_globals['__metaclass__'] = __metaclass__ restricted_globals['__name__'] = "Avatar" @@ -70,7 +61,6 @@ def _avatar_src_changed(self, new_avatar_code): def _get_new_avatar(self, src_code): self.avatar_source_code = src_code - module = imp.new_module('avatar') # Create a temporary module to execute the src_code in module.__dict__.update(restricted_globals) @@ -91,7 +81,7 @@ def _update_avatar(self, src_code): been updated, meaning that self.avatar will actually be for the last correct code """ - if self.should_update(src_code): + if self._should_update(src_code): try: self.avatar = self._get_new_avatar(src_code) except Exception as e: @@ -100,12 +90,13 @@ def _update_avatar(self, src_code): else: self.update_successful = True - def should_update(self, src_code): + def _should_update(self, src_code): return (self.avatar is None or self.auto_update and self._avatar_src_changed(src_code) or not self.update_successful) def process_avatar_turn(self, world_map, avatar_state, src_code): output_log = StringIO() + src_code = self.get_printed(src_code) avatar_updated = self._avatar_src_changed(src_code) try: @@ -139,10 +130,14 @@ def process_avatar_turn(self, world_map, avatar_state, src_code): return {'action': action, 'log': logs, 'avatar_updated': avatar_updated} def decide_action(self, world_map, avatar_state): - action = self.avatar.handle_turn(world_map, avatar_state) - if not isinstance(action, Action): - raise InvalidActionException(action) - return action.serialise() + try: + action, printed = self.avatar.handle_turn(world_map, avatar_state) + print(printed) + if not isinstance(action, Action): + raise InvalidActionException(action) + return action.serialise() + except TypeError: + raise InvalidActionException(None) def clean_logs(self, logs): getattr_pattern = "" @@ -162,3 +157,22 @@ def get_only_user_traceback(): start_of_user_traceback = i break return traceback_list[start_of_user_traceback:] + + @staticmethod + def get_printed(src_code): + """ This method adds ', printed' to the end of the handle_turn return statement. + This is due to the fact that restricted python's PrintCollector requires this + explicitly, in order to get whatever has been printed by the user's code. """ + src_code = src_code.split('\n') + new_src_code = [] + in_handle_turn = False + for line in src_code: + if "def handle_turn" == line.strip()[0:15]: + in_handle_turn = True + elif "def" == line.strip()[0:3]: + in_handle_turn = False + if "return" == line.strip()[0:6] and in_handle_turn: + line = line + ', printed' + new_src_code.append(line) + + return '\n'.join(new_src_code) diff --git a/aimmo-game-worker/service.py b/aimmo-game-worker/service.py index ef6e5f3b9..b9f03d8a3 100755 --- a/aimmo-game-worker/service.py +++ b/aimmo-game-worker/service.py @@ -42,7 +42,8 @@ def run(host, port, data_url): logging.basicConfig(level=logging.DEBUG) avatar_runner = AvatarRunner() app.config['DEBUG'] = False - app.run(host, port) + app.run(host, port, debug=False) + LOGGER.info("HI!") if __name__ == '__main__': diff --git a/aimmo-game-worker/setup.py b/aimmo-game-worker/setup.py index 48973b269..d85ff229a 100644 --- a/aimmo-game-worker/setup.py +++ b/aimmo-game-worker/setup.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- from setuptools import find_packages, setup +import coverage +import sys +import os +withcoverage = os.environ.get('WITH_COVERAGE') + +if withcoverage == 'True': + print("starting code coverage engine") + coveragedatafile = ".coverage" + cov = coverage.Coverage(config_file=False) + cov.start() + setup( name='aimmo-game-worker', packages=find_packages(), include_package_data=True, install_requires=[ 'flask', - 'requests', - 'six' + 'requests' ], tests_require=[ 'httmock', @@ -18,3 +28,8 @@ test_suite='tests', zip_safe=False, ) + +if withcoverage == 'True': + print("saving coverage stats") + cov.save() + print("exiting program") diff --git a/aimmo-game-worker/tests/tests_simulation/test_world_map.py b/aimmo-game-worker/tests/tests_simulation/test_world_map.py index 6b2457eb5..7c9b7d5a6 100644 --- a/aimmo-game-worker/tests/tests_simulation/test_world_map.py +++ b/aimmo-game-worker/tests/tests_simulation/test_world_map.py @@ -16,7 +16,7 @@ def _generate_cells(self, columns=3, rows=3): 'generates_score': False, 'avatar': None, 'pickup': None, - } for x in xrange(-columns / 2 + 1, 1 + columns / 2) for y in xrange(-rows / 2 + 1, 1 + rows / 2)] + } for x in range(-columns // 2 + 1, 1 + columns // 2) for y in range(-rows // 2 + 1, 1 + rows // 2)] return cells def assertGridSize(self, map, expected_rows, expected_columns=None): @@ -37,7 +37,7 @@ def test_grid_size(self): def test_all_cells(self): map = WorldMap(self._generate_cells()) self.assertLocationsEqual(map.all_cells(), - [Location(x, y) for x in xrange(-1, 2) for y in xrange(-1, 2)]) + [Location(x, y) for x in range(-1, 2) for y in range(-1, 2)]) def test_score_cells(self): cells = self._generate_cells() diff --git a/aimmo-game/Dockerfile b/aimmo-game/Dockerfile index 7c5a483dd..8df291525 100644 --- a/aimmo-game/Dockerfile +++ b/aimmo-game/Dockerfile @@ -1,17 +1,27 @@ -FROM python:2-alpine - +FROM python:3.6 as builder MAINTAINER code@ocado.com -ENV WORKER_MANAGER=kubernetes - -RUN apk add --no-cache gcc musl-dev python-dev libffi-dev openssl-dev - RUN pip install pipenv - COPY ["Pipfile", "Pipfile.lock", "setup.py", "./"] - +RUN pipenv install coverage RUN pipenv install --system --deploy +FROM python:3.6-alpine as base +COPY --from=builder /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages COPY . . -CMD ["python", "./service.py", "0.0.0.0", "5000"] +FROM base as runner +RUN apk add --no-cache bash +ENV WORKER_MANAGER=kubernetes +CMD python ./service.py 0.0.0.0 + +FROM base as tester +ENV WORKER_MANAGER=kubernetes +CMD python setup.py test + +FROM base as coverage_tester +ENV WORKER_MANAGER=kubernetes +ENV WITH_COVERAGE='True' +RUN apk add bash +COPY --from=builder /usr/local/bin/coverage /usr/local/bin/coverage +CMD python setup.py test diff --git a/aimmo-game/Pipfile b/aimmo-game/Pipfile index c98af800c..08ef209b9 100644 --- a/aimmo-game/Pipfile +++ b/aimmo-game/Pipfile @@ -4,10 +4,13 @@ verify_ssl = true name = "pypi" [packages] -aimmo-game = {editable = true, path = "."} +aimmo-game = {path = ".", editable = true} docker = "*" kubernetes = "*" prometheus-client = "*" +aiohttp = "*" +aiohttp-cors = "*" +coverage = "*" [requires] -python_version = "2.7" +python_version = "3.6.7" diff --git a/aimmo-game/Pipfile.lock b/aimmo-game/Pipfile.lock index 6668365a9..b563235c2 100644 --- a/aimmo-game/Pipfile.lock +++ b/aimmo-game/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "d926f064ac627d0f29b0d62258b5fd3c0fc9355ab729d228e491b74b485991ce" + "sha256": "360ec685cdd165953b512dfb38da651d3dae70c6c907966cd6eb186d0a4e69e5" }, "pipfile-spec": 6, "requires": { - "python_version": "2.7" + "python_version": "3.6.7" }, "sources": [ { @@ -27,6 +27,42 @@ "editable": true, "path": "." }, + "aiohttp": { + "hashes": [ + "sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b", + "sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08", + "sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd", + "sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac", + "sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650", + "sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa", + "sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95", + "sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330", + "sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc", + "sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b", + "sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de", + "sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4", + "sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7", + "sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b", + "sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8", + "sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd", + "sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2", + "sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698", + "sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95", + "sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6", + "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0", + "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07" + ], + "index": "pypi", + "version": "==3.4.4" + }, + "aiohttp-cors": { + "hashes": [ + "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", + "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d" + ], + "index": "pypi", + "version": "==0.7.0" + }, "asn1crypto": { "hashes": [ "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", @@ -34,19 +70,26 @@ ], "version": "==0.24.0" }, - "backports.ssl-match-hostname": { + "async-timeout": { + "hashes": [ + "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", + "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + ], + "version": "==3.0.1" + }, + "attrs": { "hashes": [ - "sha256:502ad98707319f4a51fa2ca1c677bd659008d27ded9f6380c79e8932e38dcdf2" + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" ], - "markers": "python_version < '3.5'", - "version": "==3.5.0.1" + "version": "==18.2.0" }, "cachetools": { "hashes": [ - "sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3", - "sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35" + "sha256:0a258d82933a1dd18cb540aca4ac5d5690731e24d1239a08577b814998f49785", + "sha256:4621965b0d9d4c82a79a29edbad19946f5e7702df4afae7d1ed2df951559a8cc" ], - "version": "==2.1.0" + "version": "==3.0.0" }, "certifi": { "hashes": [ @@ -106,29 +149,66 @@ ], "version": "==7.0" }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, "cryptography": { "hashes": [ - "sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb", - "sha256:10b48e848e1edb93c1d3b797c83c72b4c387ab0eb4330aaa26da8049a6cbede0", - "sha256:17db09db9d7c5de130023657be42689d1a5f60502a14f6f745f6f65a6b8195c0", - "sha256:227da3a896df1106b1a69b1e319dce218fa04395e8cc78be7e31ca94c21254bc", - "sha256:2cbaa03ac677db6c821dac3f4cdfd1461a32d0615847eedbb0df54bb7802e1f7", - "sha256:31db8febfc768e4b4bd826750a70c79c99ea423f4697d1dab764eb9f9f849519", - "sha256:4a510d268e55e2e067715d728e4ca6cd26a8e9f1f3d174faf88e6f2cb6b6c395", - "sha256:6a88d9004310a198c474d8a822ee96a6dd6c01efe66facdf17cb692512ae5bc0", - "sha256:76936ec70a9b72eb8c58314c38c55a0336a2b36de0c7ee8fb874a4547cadbd39", - "sha256:7e3b4aecc4040928efa8a7cdaf074e868af32c58ffc9bb77e7bf2c1a16783286", - "sha256:8168bcb08403ef144ff1fb880d416f49e2728101d02aaadfe9645883222c0aa5", - "sha256:8229ceb79a1792823d87779959184a1bf95768e9248c93ae9f97c7a2f60376a1", - "sha256:8a19e9f2fe69f6a44a5c156968d9fc8df56d09798d0c6a34ccc373bb186cee86", - "sha256:8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6", - "sha256:be495b8ec5a939a7605274b6e59fbc35e76f5ad814ae010eb679529671c9e119", - "sha256:dc2d3f3b1548f4d11786616cf0f4415e25b0fbecb8a1d2cd8c07568f13fdde38", - "sha256:e4aecdd9d5a3d06c337894c9a6e2961898d3f64fe54ca920a72234a3de0f9cb3", - "sha256:e79ab4485b99eacb2166f3212218dd858258f374855e1568f728462b0e6ee0d9", - "sha256:f995d3667301e1754c57b04e0bae6f0fa9d710697a9f8d6712e8cca02550910f" - ], - "version": "==2.3.1" + "sha256:02915ee546b42ce513e8167140e9937fc4c81a06a82216e086ccce51f347948a", + "sha256:03cc8bc5a69ae3d44acf1a03facdb7c10a94c67907862c563e10efe72b737977", + "sha256:07f76bde6815c55195f3b3812d35769cc7c765144c0bb71ae45e02535d078591", + "sha256:13eac1c477b9af7e9a9024369468d08aead6ad78ed599d163ad046684474364b", + "sha256:179bfb585c5efc87ae0e665770e4896727b92dbc1f810c761b1ebf8363e2fec8", + "sha256:414af0ba308e74c1f8bc5b11befc86cb66b10be8959547786f64258830d2096f", + "sha256:41a1ca14f255df8c44dd22c6006441d631d1589104045ec7263cc47e9772f41a", + "sha256:54947eb98bc4eef99ddf49f45d2694ea5a3929ab3edc9806ad01967368594d82", + "sha256:5bac7a2abda07d0c3c8429210349bb54149ad8940dc7bcffedcd56519b410a3c", + "sha256:7f41af8c586bed9f59cfe8832d818b3b75c860d7025da9cd2db76875a72ff785", + "sha256:8004fae1b3cb2dbd90a011ad972e49a7e78a871b89c70cc7213cf4ebd2532bcb", + "sha256:8e0eccadc3b465e12c50a5b8fb4d39cf401b44d7bb9936c70fddb5e5aaf740d5", + "sha256:95b4741722269cfdc134fec23b7ae6503ee2aea83d0924cfee6d6ec54cd42d8e", + "sha256:a06f5aa6d7a94531dfe82eb2972e669258c452fe9cf88f76116610de4c789785", + "sha256:b0833d27c7eb536bc27323a1e8e22cb39ebac78c4ef3be0167ba40f447344808", + "sha256:b72dec675bc59a01edc96616cd48ec465b714481caa0938c8bbca5d18f17d5df", + "sha256:c800ddc23b5206ce025f23225fdde89cdc0e64016ad914d5be32d1f602ce9495", + "sha256:c980c8c313a5e014ae12e2245e89e7b30427e5a98cbb88afe478ecae85f3abaa", + "sha256:e85b410885addaeb31a867eabcefc9ef4a7e904ad45eac9e60a763a54b244626" + ], + "version": "==2.4.1" }, "dnspython": { "hashes": [ @@ -152,16 +232,6 @@ ], "version": "==0.3.0" }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "markers": "python_version < '3.4'", - "version": "==1.1.6" - }, "eventlet": { "hashes": [ "sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8", @@ -178,17 +248,17 @@ }, "flask-cors": { "hashes": [ - "sha256:e4c8fc15d3e4b4cce6d3b325f2bab91e0e09811a61f50d7a53493bc44242a4f1", - "sha256:ecc016c5b32fa5da813ec8d272941cfddf5f6bba9060c405a70285415cbf24c9" + "sha256:7ad56ee3b90d4955148fc25a2ecaa1124fc84298471e266a7fea59aeac4405a5", + "sha256:7e90bf225fdf163d11b84b59fb17594d0580a16b97ab4e1146b1fb2737c1cfec" ], - "version": "==3.0.6" + "version": "==3.0.7" }, "google-auth": { "hashes": [ - "sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530", - "sha256:a4cf9e803f2176b5de442763bd339b313d3f1ed3002e3e1eb6eec1d7c9bbc9b4" + "sha256:494e747bdc2cdeb0fa6ef85118de2ea1a563f160294cce05048c6ff563fda1bb", + "sha256:b08a27888e9d1c17a891b3688aacc9c6f2019d7f6c5a2e73588e6bb9a2c0fa98" ], - "version": "==1.5.1" + "version": "==1.6.1" }, "greenlet": { "hashes": [ @@ -221,13 +291,12 @@ ], "version": "==2.7" }, - "ipaddress": { + "idna-ssl": { "hashes": [ - "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", - "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" + "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" ], - "markers": "python_version < '3.3'", - "version": "==1.0.22" + "markers": "python_version < '3.7'", + "version": "==1.1.0" }, "itsdangerous": { "hashes": [ @@ -245,17 +314,44 @@ }, "kubernetes": { "hashes": [ - "sha256:5ee6e2e949ca800ad8a73da6f67c2a637c2c803945b006e6105beae83e43b273", - "sha256:84dfb4319afac189e8327b71b9332b5329d2a78074f58958c5f06a870edf32ba" + "sha256:0cc9ce02d838da660efa0a67270b4b7d47e6beb8889673cd45c86f897e2d6821", + "sha256:54f8e7bb1dd9a55cf416dff76a63c4ae441764280942d9913f2243676f29d02c" ], "index": "pypi", - "version": "==7.0.0" + "version": "==8.0.0" }, "markupsafe": { "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" ], - "version": "==1.0" + "version": "==1.1.0" }, "monotonic": { "hashes": [ @@ -264,6 +360,40 @@ ], "version": "==1.5" }, + "multidict": { + "hashes": [ + "sha256:05eeab69bf2b0664644c62bd92fabb045163e5b8d4376a31dfb52ce0210ced7b", + "sha256:0c85880efa7cadb18e3b5eef0aa075dc9c0a3064cbbaef2e20be264b9cf47a64", + "sha256:136f5a4a6a4adeacc4dc820b8b22f0a378fb74f326e259c54d1817639d1d40a0", + "sha256:14906ad3347c7d03e9101749b16611cf2028547716d0840838d3c5e2b3b0f2d3", + "sha256:1ade4a3b71b1bf9e90c5f3d034a87fe4949c087ef1f6cd727fdd766fe8bbd121", + "sha256:22939a00a511a59f9ecc0158b8db728afef57975ce3782b3a265a319d05b9b12", + "sha256:2b86b02d872bc5ba5b3a4530f6a7ba0b541458ab4f7c1429a12ac326231203f7", + "sha256:3c11e92c3dfc321014e22fb442bc9eb70e01af30d6ce442026b0c35723448c66", + "sha256:4ba3bd26f282b201fdbce351f1c5d17ceb224cbedb73d6e96e6ce391b354aacc", + "sha256:4c6e78d042e93751f60672989efbd6a6bc54213ed7ff695fff82784bbb9ea035", + "sha256:4d80d1901b89cc935a6cf5b9fd89df66565272722fe2e5473168927a9937e0ca", + "sha256:4fcf71d33178a00cc34a57b29f5dab1734b9ce0f1c97fb34666deefac6f92037", + "sha256:52f7670b41d4b4d97866ebc38121de8bcb9813128b7c4942b07794d08193c0ab", + "sha256:5368e2b7649a26b7253c6c9e53241248aab9da49099442f5be238fde436f18c9", + "sha256:5bb65fbb48999044938f0c0508e929b14a9b8bf4939d8263e9ea6691f7b54663", + "sha256:60672bb5577472800fcca1ac9dae232d1461db9f20f055184be8ce54b0052572", + "sha256:669e9be6d148fc0283f53e17dd140cde4dc7c87edac8319147edd5aa2a830771", + "sha256:6a0b7a804e8d1716aa2c72e73210b48be83d25ba9ec5cf52cf91122285707bb1", + "sha256:79034ea3da3cf2a815e3e52afdc1f6c1894468c98bdce5d2546fa2342585497f", + "sha256:79247feeef6abcc11137ad17922e865052f23447152059402fc320f99ff544bb", + "sha256:81671c2049e6bf42c7fd11a060f8bc58f58b7b3d6f3f951fc0b15e376a6a5a98", + "sha256:82ac4a5cb56cc9280d4ae52c2d2ebcd6e0668dd0f9ef17f0a9d7c82bd61e24fa", + "sha256:9436267dbbaa49dad18fbbb54f85386b0f5818d055e7b8e01d219661b6745279", + "sha256:94e4140bb1343115a1afd6d84ebf8fca5fb7bfb50e1c2cbd6f2fb5d3117ef102", + "sha256:a2cab366eae8a0ffe0813fd8e335cf0d6b9bb6c5227315f53bb457519b811537", + "sha256:a596019c3eafb1b0ae07db9f55a08578b43c79adb1fe1ab1fd818430ae59ee6f", + "sha256:e8848ae3cd6a784c29fae5055028bee9bffcc704d8bcad09bd46b42b44a833e2", + "sha256:e8a048bfd7d5a280f27527d11449a509ddedf08b58a09a24314828631c099306", + "sha256:f6dd28a0ac60e2426a6918f36f1b4e2620fc785a0de7654cd206ba842eee57fd" + ], + "version": "==4.4.2" + }, "oauthlib": { "hashes": [ "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162", @@ -280,37 +410,15 @@ }, "pyasn1": { "hashes": [ - "sha256:0ad0fe0593dde1e599cac0bf65bb1a4ec663032f0bc68ee44850db4251e8c501", - "sha256:13794d835643ee970b2c059dbfe4eb5d751e16c693c8baee61c526abd209e5c7", - "sha256:49a8ed515f26913049113820b462f698e6ed26df62c389dafb6fa3685ddca8de", - "sha256:74ac8521a0480f228549be20bea555ae35678f0e754c2fbc6f1576b0959bec43", - "sha256:89399ca8ecd4524f974e926d4ef9e7a787903e01f0a9cdff3131ad1361792fe5", - "sha256:8f291e0338d519a1a0d07f0b9d03c9265f6be26eb32fdd21af6d3259d14ea49c", "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", - "sha256:d3bbd726c1a760d4ca596a4d450c380b81737612fe0182f5bb3caebc17461fd9", - "sha256:dea873d6c907c1cf1341fd88742a61efce33227d7743cb37564ab7d7e77dd9fd", - "sha256:ded5eea5cb88bc1ce9aa074b5a3092f95ce4741887e317e9b49c7ece75d7ea0e", - "sha256:e8b69ea2200d42201cbedd486eedb8980f320d4534f83ce2fb468e96aa5545d0", - "sha256:edad117649643230493aeb4955456ce19ab4b12e94489dde6f7094cdb5a3c87e", "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" ], "version": "==0.4.4" }, "pyasn1-modules": { "hashes": [ - "sha256:077250b34432520430bc1c80dcbda4e354090785567c33ded35faa6df8d24753", - "sha256:0da2f947e8ad2697e86fe5fd0e55a4093a2fd79d839c9e19c34e28097db7002c", - "sha256:35ff894a0b5df8e28b700126b2869c7dcfb2b2db5bc82e5d5e82547069241553", - "sha256:44688b94841349648b1e1a5a7a3d96e6596d5d4f21d0b59a82307e153c4dc74b", - "sha256:833716dde880a7f2f2ccdeea9a096842626981ff2a477d8b318c0906367ac11b", "sha256:a0cf3e1842e7c60fde97cb22d275eb6f9524f5c5250489e292529de841417547", - "sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e", - "sha256:a728bb9502d1fdc104c66f24a176b6a70a32e89d1d8a5b55c959233ed51c67be", - "sha256:c30a098435ea0989c37005a971843e9d3966c7f6d056ddbf052e5061c06e3291", - "sha256:c355a45b32c5bc1d9893eceb704b0cfcd1126f91b5a7b9ee64c1c05383283381", - "sha256:e64679de1940f41ead5170fce364d54e7b9e2e862f064727b6bcb5cee753b7a2", - "sha256:ed71d20225c356881c29f0b1d7a0d6521563a389d9478e8f95d798cc5ba07b88", - "sha256:f183f0940b9f5ed2ad9d04c80cab2451440fa9af4fc959d85113fadd2e777962" + "sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e" ], "version": "==0.2.2" }, @@ -366,16 +474,15 @@ }, "requests": { "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], - "version": "==2.20.0" + "version": "==2.20.1" }, "requests-oauthlib": { "hashes": [ "sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f", - "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8", - "sha256:fe3282f48fb134ee0035712159f5429215459407f6d5484013343031ff1a400d" + "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8" ], "version": "==1.0.0" }, @@ -395,17 +502,17 @@ }, "urllib3": { "hashes": [ - "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", - "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "version": "==1.24" + "version": "==1.24.1" }, "websocket-client": { "hashes": [ - "sha256:c42b71b68f9ef151433d6dcc6a7cb98ac72d2ad1e3a74981ca22bc5d9134f166", - "sha256:f5889b1d0a994258cfcbc8f2dc3e457f6fc7b32a8d74873033d12e4eab4bdf63" + "sha256:8c8bf2d4f800c3ed952df206b18c28f7070d9e3dcbd6ca6291127574f57ee786", + "sha256:e51562c91ddb8148e791f0155fdb01325d99bb52c4cdbb291aee7a3563fd0849" ], - "version": "==0.53.0" + "version": "==0.54.0" }, "werkzeug": { "hashes": [ @@ -413,6 +520,20 @@ "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" ], "version": "==0.14.1" + }, + "yarl": { + "hashes": [ + "sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9", + "sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee", + "sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308", + "sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357", + "sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78", + "sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8", + "sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1", + "sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4", + "sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7" + ], + "version": "==1.2.6" } }, "develop": {} diff --git a/aimmo-game/service.py b/aimmo-game/service.py index e293fa820..33259edc4 100644 --- a/aimmo-game/service.py +++ b/aimmo-game/service.py @@ -1,26 +1,28 @@ #!/usr/bin/env python -import cPickle as pickle +from aiohttp import web +import aiohttp_cors +import asyncio +import pickle +import ast import logging import os import sys -from urlparse import parse_qs +import json +from urllib.parse import parse_qs -import eventlet -import flask import socketio -from flask_cors import CORS from simulation import map_generator from simulation.worker_managers import WORKER_MANAGERS from simulation.game_runner import GameRunner -eventlet.sleep() -eventlet.monkey_patch() +app = web.Application() +cors = aiohttp_cors.setup(app) -flask_app = flask.Flask(__name__) -CORS(flask_app, supports_credentials=True) -socketio_server = socketio.Server() +socketio_server = socketio.AsyncServer(async_handlers=True) + +routes = web.RouteTableDef() LOGGER = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -38,17 +40,20 @@ def register_endpoints(self): self.register_world_update_on_connect() self.register_remove_session_id_from_mappings() self.register_healthcheck() + app.add_routes(routes) def register_healthcheck(self): - @flask_app.route('/game-') - def healthcheck(game_id): - return 'HEALTHY' + @routes.get('/game-{game_id}') + async def healthcheck(request): + return web.Response(text='HEALTHY') + + return healthcheck def register_player_data_view(self): - @flask_app.route('/player/') - def player_data(player_id): - player_id = int(player_id) - return flask.jsonify({ + @routes.get('/player/{player_id}') + async def player_data(request: web.Request): + player_id = int(request.match_info['player_id']) + return web.json_response({ 'code': self.worker_manager.get_code(player_id), 'options': {}, 'state': None, @@ -58,16 +63,16 @@ def player_data(player_id): def register_world_update_on_connect(self): @socketio_server.on('connect') - def world_update_on_connect(sid, environ): + async def world_update_on_connect(sid, environ): query = environ['QUERY_STRING'] self._find_avatar_id_from_query(sid, query) - self.send_updates() + await self.send_updates() return world_update_on_connect def register_remove_session_id_from_mappings(self): @socketio_server.on('disconnect') - def remove_session_id_from_mappings(sid): + async def remove_session_id_from_mappings(sid): LOGGER.info("Socket disconnected for session id:{}. ".format(sid)) try: del self._socket_session_id_to_player_id[sid] @@ -76,11 +81,11 @@ def remove_session_id_from_mappings(sid): return remove_session_id_from_mappings - def send_updates(self): - self._send_game_state() + async def send_updates(self): + await self._send_game_state() player_id_to_worker = self.worker_manager.player_id_to_worker - self._send_logs(player_id_to_worker) - self._send_have_avatars_code_updated(player_id_to_worker) + await self._send_logs(player_id_to_worker) + await self._send_have_avatars_code_updated(player_id_to_worker) def _find_avatar_id_from_query(self, session_id, query_string): """ @@ -99,34 +104,33 @@ def _find_avatar_id_from_query(self, session_id, query_string): LOGGER.error("No avatar ID found. User may not be authorised ") LOGGER.error("query_string: " + query_string) - def _send_logs(self, player_id_to_workers): + async def _send_logs(self, player_id_to_workers): def should_send_logs(logs): return bool(logs) socket_session_id_to_player_id_copy = self._socket_session_id_to_player_id.copy() - for sid, player_id in socket_session_id_to_player_id_copy.iteritems(): + for sid, player_id in socket_session_id_to_player_id_copy.items(): avatar_logs = player_id_to_workers[player_id].log if should_send_logs(avatar_logs): - socketio_server.emit('log', avatar_logs, room=sid) + await socketio_server.emit('log', avatar_logs, room=sid) - def _send_game_state(self): + async def _send_game_state(self): serialised_game_state = self.game_state.serialise() socket_session_id_to_player_id_copy = self._socket_session_id_to_player_id.copy() - for sid, player_id in socket_session_id_to_player_id_copy.iteritems(): - socketio_server.emit('game-state', serialised_game_state, room=sid) + for sid, player_id in socket_session_id_to_player_id_copy.items(): + await socketio_server.emit('game-state', serialised_game_state, room=sid) - def _send_have_avatars_code_updated(self, player_id_to_workers): + async def _send_have_avatars_code_updated(self, player_id_to_workers): socket_session_id_to_player_id_copy = self._socket_session_id_to_player_id.copy() - for sid, player_id in socket_session_id_to_player_id_copy.iteritems(): + for sid, player_id in socket_session_id_to_player_id_copy.items(): if player_id_to_workers[player_id].has_code_updated: - socketio_server.emit('feedback-avatar-updated', room=sid) + await socketio_server.emit('feedback-avatar-updated', room=sid) def create_runner(port): - settings = pickle.loads(os.environ['settings']) + settings = json.loads(os.environ['settings']) generator = getattr(map_generator, settings['GENERATOR'])(settings) worker_manager_class = WORKER_MANAGERS[os.environ.get('WORKER_MANAGER', 'local')] - return GameRunner(worker_manager_class=worker_manager_class, game_state_generator=generator.get_game_state, django_api_url=os.environ.get('GAME_API_URL', 'http://localhost:8000/aimmo/api/games/'), @@ -138,14 +142,14 @@ def run_game(port): game_api = GameAPI(game_state=game_runner.game_state, worker_manager=game_runner.worker_manager) game_runner.set_end_turn_callback(game_api.send_updates) - game_runner.start() + asyncio.ensure_future(game_runner.run()) if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) host = sys.argv[1] - socket_app = socketio.Middleware(socketio_server, flask_app, - socketio_path=os.environ.get('SOCKETIO_RESOURCE', 'socket.io')) + + socketio_server.attach(app, socketio_path=os.environ.get('SOCKETIO_RESOURCE', 'socket.io')) if os.environ['WORKER_MANAGER'] == 'local': port = int(os.environ['EXTERNAL_PORT']) @@ -153,4 +157,6 @@ def run_game(port): port = int(sys.argv[2]) run_game(port) - eventlet.wsgi.server(eventlet.listen((host, port)), socket_app, debug=False) + + LOGGER.info("starting the server") + web.run_app(app, host=host, port=port) diff --git a/aimmo-game/setup.cfg b/aimmo-game/setup.cfg new file mode 100644 index 000000000..81f105269 --- /dev/null +++ b/aimmo-game/setup.cfg @@ -0,0 +1,5 @@ +[aliases] +test=pytest + +[tool:pytest] +testpaths=tests diff --git a/aimmo-game/setup.py b/aimmo-game/setup.py index 19ea55a4e..7b0e89209 100644 --- a/aimmo-game/setup.py +++ b/aimmo-game/setup.py @@ -1,5 +1,17 @@ # -*- coding: utf-8 -*- from setuptools import find_packages, setup +import coverage +import sys +import os + + +withcoverage = os.environ.get('WITH_COVERAGE') + +if withcoverage == 'True': + print("starting code coverage engine") + coveragedatafile = ".coverage" + cov = coverage.Coverage(config_file=False) + cov.start() setup( @@ -15,10 +27,22 @@ 'six', 'kubernetes' ], + setup_requires=[ + "pytest-runner" + ], tests_require=[ + 'pytest', + 'pytest-asyncio', + 'asynctest', 'httmock', - 'mock' + 'mock', + 'hypothesis' ], test_suite='tests', zip_safe=False, ) + +if withcoverage == 'True': + print("saving coverage stats") + cov.save() + print("exiting program") diff --git a/aimmo-game/simulation/action.py b/aimmo-game/simulation/action.py index e7b28f220..f06beda28 100644 --- a/aimmo-game/simulation/action.py +++ b/aimmo-game/simulation/action.py @@ -123,9 +123,6 @@ def _apply(self, world_map): damage_dealt)) attacked_avatar.damage(damage_dealt) - LOGGER.debug('{} dealt {} damage to {}'.format(self.avatar, - damage_dealt, - attacked_avatar)) self.avatar.clear_action() if attacked_avatar.health <= 0: diff --git a/aimmo-game/simulation/avatar/avatar_manager.py b/aimmo-game/simulation/avatar/avatar_manager.py index 8b101582a..b086dff60 100644 --- a/aimmo-game/simulation/avatar/avatar_manager.py +++ b/aimmo-game/simulation/avatar/avatar_manager.py @@ -24,7 +24,7 @@ def remove_avatar(self, user_id): @property def avatars(self): - return list(self.avatars_by_id.viewvalues()) + return list(self.avatars_by_id.values()) @property def active_avatars(self): diff --git a/aimmo-game/simulation/game_logic/__init__.py b/aimmo-game/simulation/game_logic/__init__.py index 9d9a8ba58..069fa2055 100644 --- a/aimmo-game/simulation/game_logic/__init__.py +++ b/aimmo-game/simulation/game_logic/__init__.py @@ -1,5 +1,5 @@ from .spawn_location_finder import SpawnLocationFinder -from map_updaters import ScoreLocationUpdater, MapContext, PickupUpdater, MapExpander +from .map_updaters import ScoreLocationUpdater, MapContext, PickupUpdater, MapExpander __all__ = [ 'SpawnLocationFinder', diff --git a/aimmo-game/simulation/game_logic/map_updaters.py b/aimmo-game/simulation/game_logic/map_updaters.py index f5184a1e8..b02506afd 100644 --- a/aimmo-game/simulation/game_logic/map_updaters.py +++ b/aimmo-game/simulation/game_logic/map_updaters.py @@ -41,7 +41,6 @@ def update(self, world_map, context): target_num_pickups = int(math.ceil( context.num_avatars * world_map.settings['TARGET_NUM_PICKUPS_PER_AVATAR'] )) - LOGGER.debug('Aiming for %s new pickups', target_num_pickups) max_num_pickups_to_add = target_num_pickups - len(list(world_map.pickup_cells())) locations = world_map._spawn_location_finder.get_random_spawn_locations(max_num_pickups_to_add) for cell in locations: diff --git a/aimmo-game/simulation/game_logic/spawn_location_finder.py b/aimmo-game/simulation/game_logic/spawn_location_finder.py index 626adaadf..9e77d12da 100644 --- a/aimmo-game/simulation/game_logic/spawn_location_finder.py +++ b/aimmo-game/simulation/game_logic/spawn_location_finder.py @@ -26,7 +26,6 @@ def get_random_spawn_locations(self, max_locations): try: return random.sample(potential_locations, max_locations) except ValueError: - LOGGER.debug('Not enough potential locations') return potential_locations def get_random_spawn_location(self): diff --git a/aimmo-game/simulation/game_runner.py b/aimmo-game/simulation/game_runner.py index 33cc50b44..3ea6d5d39 100644 --- a/aimmo-game/simulation/game_runner.py +++ b/aimmo-game/simulation/game_runner.py @@ -1,16 +1,22 @@ import time import threading +import logging +import asyncio +import concurrent.futures -from django_communicator import DjangoCommunicator -from simulation_runner import ConcurrentSimulationRunner -from avatar.avatar_manager import AvatarManager +from simulation.django_communicator import DjangoCommunicator +from simulation.simulation_runner import ConcurrentSimulationRunner +from simulation.avatar.avatar_manager import AvatarManager + +LOGGER = logging.getLogger(__name__) TURN_TIME = 2 -class GameRunner(threading.Thread): +class GameRunner: def __init__(self, worker_manager_class, game_state_generator, django_api_url, port): super(GameRunner, self).__init__() + self.worker_manager = worker_manager_class(port=port) self.game_state = game_state_generator(AvatarManager()) self.communicator = DjangoCommunicator(django_api_url=django_api_url, @@ -42,6 +48,7 @@ def update_workers(self): game_metadata = self.communicator.get_game_metadata()['main'] users_to_add = self.get_users_to_add(game_metadata) + LOGGER.info(users_to_add) users_to_delete = self.get_users_to_delete(game_metadata) self.worker_manager.add_workers(users_to_add) @@ -53,16 +60,16 @@ def update_workers(self): self.update_main_user(game_metadata) self.worker_manager.fetch_all_worker_data(self.game_state.get_serialised_game_states_for_workers()) - def update_simulation(self, player_id_to_serialised_actions): + async def update_simulation(self, player_id_to_serialised_actions): self.simulation_runner.run_single_turn(player_id_to_serialised_actions) - self._end_turn_callback() + await self._end_turn_callback() - def update(self): + async def update(self): self.update_workers() - self.update_simulation(self.worker_manager.get_player_id_to_serialised_actions()) + await self.update_simulation(self.worker_manager.get_player_id_to_serialised_actions()) self.worker_manager.clear_logs() - def run(self): + async def run(self): while True: - self.update() - time.sleep(TURN_TIME) + await self.update() + await asyncio.sleep(TURN_TIME) diff --git a/aimmo-game/simulation/game_state.py b/aimmo-game/simulation/game_state.py index 839001a57..f87a4df41 100644 --- a/aimmo-game/simulation/game_state.py +++ b/aimmo-game/simulation/game_state.py @@ -1,5 +1,5 @@ from threading import RLock -from pickups import serialise_pickups +from simulation.pickups import serialise_pickups class GameState(object): @@ -79,4 +79,4 @@ def serialise_for_worker(self, avatar_wrapper): def get_serialised_game_states_for_workers(self): with self._lock: return {player_id: self.serialise_for_worker(avatar_wrapper) for player_id, avatar_wrapper - in self.avatar_manager.avatars_by_id.iteritems()} + in self.avatar_manager.avatars_by_id.items()} diff --git a/aimmo-game/simulation/map_generator.py b/aimmo-game/simulation/map_generator.py index 7c7273ac8..11df189d4 100644 --- a/aimmo-game/simulation/map_generator.py +++ b/aimmo-game/simulation/map_generator.py @@ -3,6 +3,8 @@ import logging import random from itertools import tee +from queue import PriorityQueue +from typing import Any from six.moves import zip, range @@ -92,11 +94,11 @@ def manhattan_distance_to_destination_cell(this_branch): y_distance = abs(branch_tip_location.y - destination_cell.location.y) return x_distance + y_distance + len(this_branch) - branches = PriorityQueue(key=manhattan_distance_to_destination_cell, - init_items=[[source_cell]]) + branches = PriorityQueuef(key=manhattan_distance_to_destination_cell, + init_items=[[source_cell]]) visited_cells = set() - while branches: + while not branches.queue.empty(): branch = branches.pop() for cell in get_adjacent_habitable_cells(branch[-1], world_map): @@ -150,25 +152,39 @@ def get_adjacent_habitable_cells(cell, world_map): return [c for c in adjacent_cells if c.habitable] -class PriorityQueue(object): +class PriorityEntry(object): + priority: int + value: Any + + def __init__(self, priority, value): + self.priority = priority + self.value = value + + def __lt__(self, other): + return self.priority < other.priority + + +class PriorityQueuef(object): def __init__(self, key, init_items=tuple()): self.key = key self.heap = [self._build_tuple(i) for i in init_items] - heapq.heapify(self.heap) + self.queue = PriorityQueue() + for item in self.heap: + self.queue.put(item, block=True) def _build_tuple(self, item): - return self.key(item), item + return PriorityEntry(self.key(item), item) def push(self, item): to_push = self._build_tuple(item) - heapq.heappush(self.heap, to_push) + self.queue.put(to_push, block=True) def pop(self): - _, item = heapq.heappop(self.heap) - return item + entry: PriorityEntry = self.queue.get(block=True) + return entry.value def __len__(self): - return len(self.heap) + return self.queue.qsize() class _BaseLevelGenerator(_BaseGenerator): diff --git a/aimmo-game/simulation/pickups.py b/aimmo-game/simulation/pickups.py index b7d0effb1..e8b9f069b 100644 --- a/aimmo-game/simulation/pickups.py +++ b/aimmo-game/simulation/pickups.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod, abstractproperty -import effects +import simulation.effects as effects DAMAGE_BOOST_DEFAULT = 5 HEALTH_RESTORE_DEFAULT = 3 diff --git a/aimmo-game/simulation/simulation_runner.py b/aimmo-game/simulation/simulation_runner.py index b314639d0..008ee085d 100644 --- a/aimmo-game/simulation/simulation_runner.py +++ b/aimmo-game/simulation/simulation_runner.py @@ -1,8 +1,9 @@ import logging from abc import ABCMeta, abstractmethod -from threading import Thread - +from concurrent import futures +from concurrent.futures import ALL_COMPLETED from simulation.action import PRIORITIES +from threading import Thread LOGGER = logging.getLogger(__name__) @@ -69,6 +70,12 @@ def run_turn(self, player_id_to_serialised_actions): class ConcurrentSimulationRunner(SimulationRunner): + def _parallel_map(self, func, iterable_args): + with futures.ThreadPoolExecutor() as executor: + results = executor.map(func, iterable_args) + futures.wait(results, timeout=2, return_when=ALL_COMPLETED) + return [results] + def run_turn(self, player_id_to_serialised_actions): """ Concurrently get the intended actions from all avatars and register @@ -76,7 +83,6 @@ def run_turn(self, player_id_to_serialised_actions): """ avatars = self.game_state.avatar_manager.active_avatars - threads = [Thread(target=self._run_turn_for_avatar, args=(avatar, player_id_to_serialised_actions[avatar.player_id])) for avatar in avatars] diff --git a/aimmo-game/simulation/worker_managers/__init__.py b/aimmo-game/simulation/worker_managers/__init__.py index f5bdc1f3a..9d5069188 100644 --- a/aimmo-game/simulation/worker_managers/__init__.py +++ b/aimmo-game/simulation/worker_managers/__init__.py @@ -1,5 +1,5 @@ -from kubernetes_worker_manager import KubernetesWorkerManager as _KWorkerManager -from local_worker_manager import LocalWorkerManager as _LWorkerManager +from .kubernetes_worker_manager import KubernetesWorkerManager as _KWorkerManager +from .local_worker_manager import LocalWorkerManager as _LWorkerManager WORKER_MANAGERS = { 'local': _LWorkerManager, diff --git a/aimmo-game/simulation/worker_managers/kubernetes_worker_manager.py b/aimmo-game/simulation/worker_managers/kubernetes_worker_manager.py index e9d098e2e..0c0a846aa 100644 --- a/aimmo-game/simulation/worker_managers/kubernetes_worker_manager.py +++ b/aimmo-game/simulation/worker_managers/kubernetes_worker_manager.py @@ -2,8 +2,8 @@ import os import time -import kubernetes.client -import kubernetes.config +from kubernetes import client +from kubernetes import config from .worker_manager import WorkerManager @@ -16,8 +16,8 @@ class KubernetesWorkerManager(WorkerManager): """Kubernetes worker manager.""" def __init__(self, *args, **kwargs): - kubernetes.config.load_incluster_config() - self.api = kubernetes.client.CoreV1Api() + config.load_incluster_config() + self.api = client.CoreV1Api() self.game_id = os.environ['GAME_ID'] self.game_url = os.environ['GAME_URL'] self.pod_name = os.environ['POD_NAME'] @@ -29,7 +29,7 @@ def _create_a_label_selector_from_labels(label_list): def _make_owner_references(self): try: - return [kubernetes.client.V1OwnerReference( + return [client.V1OwnerReference( api_version="v1", block_owner_deletion=True, controller=True, @@ -42,8 +42,8 @@ def _make_owner_references(self): return [] def _make_container(self, player_id): - return kubernetes.client.V1Container( - env=[kubernetes.client.V1EnvVar( + return client.V1Container( + env=[client.V1EnvVar( name='DATA_URL', value='%s/player/%d' % (self.game_url, player_id)), kubernetes.client.V1EnvVar( @@ -51,21 +51,22 @@ def _make_container(self, player_id): value='5000')], name='aimmo-game-worker', image='ocadotechnology/aimmo-game-worker:%s' % os.environ.get('IMAGE_SUFFIX', 'latest'), - ports=[kubernetes.client.V1ContainerPort( + ports=[client.V1ContainerPort( container_port=5000, protocol='TCP')], - resources=kubernetes.client.V1ResourceRequirements( + resources=client.V1ResourceRequirements( limits={'cpu': '10m', 'memory': '64Mi'}, requests={'cpu': '7m', 'memory': '32Mi'}), - security_context=kubernetes.client.V1SecurityContext( - capabilities=kubernetes.client.V1Capabilities( + security_context=client.V1SecurityContext( + capabilities=client.V1Capabilities( drop=['all'], add=['NET_BIND_SERVICE'])) ) def make_pod(self, player_id): - pod_manifest = kubernetes.client.V1PodSpec(containers=[self._make_container(player_id)], service_account_name='worker') - metadata = kubernetes.client.V1ObjectMeta( + pod_manifest = client.V1PodSpec(containers=[self._make_container(player_id)], service_account_name='worker') + + metadata = client.V1ObjectMeta( labels={ 'app': 'aimmo-game-worker', 'game': self.game_id, @@ -73,12 +74,11 @@ def make_pod(self, player_id): generate_name='aimmo-%s-worker-%s-' % (self.game_id, player_id), owner_references=self._make_owner_references() ) - - return kubernetes.client.V1Pod(metadata=metadata, spec=pod_manifest) + return client.V1Pod(metadata=metadata, spec=pod_manifest) def _get_game_uid(self): pod_list = self.api.list_namespaced_pod(namespace=K8S_NAMESPACE, - field_selector='metadata.name={}'.format(self.pod_name)) + field_selector=f'metadata.name={self.pod_name}') pod_metadata = pod_list.items[0].metadata return pod_metadata.uid @@ -118,4 +118,4 @@ def remove_worker(self, player_id): for pod in pods.items: LOGGER.info('Deleting pod: {}'.format(pod.metadata.name)) - self.api.delete_namespaced_pod(pod.metadata.name, K8S_NAMESPACE, kubernetes.client.V1DeleteOptions()) + self.api.delete_namespaced_pod(pod.metadata.name, K8S_NAMESPACE, client.V1DeleteOptions()) diff --git a/aimmo-game/simulation/worker_managers/local_worker_manager.py b/aimmo-game/simulation/worker_managers/local_worker_manager.py index 5012b742a..7797623a6 100644 --- a/aimmo-game/simulation/worker_managers/local_worker_manager.py +++ b/aimmo-game/simulation/worker_managers/local_worker_manager.py @@ -29,21 +29,23 @@ def __init__(self, *args, **kwargs): def create_worker(self, player_id): assert(player_id not in self.workers) - port = self.port_counter.next() - - env = json.loads(os.environ.get('CONTAINER_TEMPLATE', '{}')) + port = next(self.port_counter) + + template_string = os.environ.get('CONTAINER_TEMPLATE') + if template_string: + template = json.loads(template_string) + else: + template = { + 'environment': {} + } data_url = 'http://{}:{}/player/{}'.format(self.host, self.port, player_id) - env['DATA_URL'] = data_url - env['PORT'] = port - + template['environment']['DATA_URL'] = data_url + template['environment']['PORT'] = port container = self.client.containers.run( name="aimmo-{}-worker-{}".format(self.game_id, player_id), image='ocadotechnology/aimmo-game-worker:test', - publish_all_ports=True, - environment=env, - network_mode='host', - detach=True, - ports={"{}/tcp".format(port): port}) + ports={f"{port}/tcp": port}, + **template) self.workers[player_id] = container worker_url = 'http://%s:%d' % ( self.host, diff --git a/aimmo-game/simulation/worker_managers/worker_manager.py b/aimmo-game/simulation/worker_managers/worker_manager.py index db9d9c600..f1d61480e 100644 --- a/aimmo-game/simulation/worker_managers/worker_manager.py +++ b/aimmo-game/simulation/worker_managers/worker_manager.py @@ -1,9 +1,9 @@ import logging import time -from eventlet.greenpool import GreenPool from eventlet.semaphore import Semaphore from threading import Thread +from concurrent import futures from simulation.worker import Worker @@ -31,7 +31,6 @@ class WorkerManager(object): """ def __init__(self, port=5000): self._data = _WorkerManagerData({}) - self._pool = GreenPool(size=3) self.player_id_to_worker = {} self.port = port @@ -45,7 +44,7 @@ def fetch_all_worker_data(self, player_id_to_game_state): """ def prepare_request_threads(): return [Thread(target=worker.fetch_data, - args=(player_id_to_game_state[player_id],)) for (player_id, worker) in self.player_id_to_worker.iteritems()] + args=(player_id_to_game_state[player_id],)) for (player_id, worker) in self.player_id_to_worker.items()] def timed_process_for_worker_turn_requests(duration): threads = prepare_request_threads() @@ -77,7 +76,8 @@ def add_new_worker(self, player_id): self.player_id_to_worker[player_id] = Worker('{}/turn/'.format(worker_url_base)) def _parallel_map(self, func, iterable_args): - return list(self._pool.imap(func, iterable_args)) + with futures.ThreadPoolExecutor() as executor: + results = executor.map(func, iterable_args) def add_workers(self, users_to_add): self._parallel_map(self.add_new_worker, users_to_add) diff --git a/aimmo-game/tests/functional/test_damage_pickups_and_effects.py b/aimmo-game/tests/functional/test_damage_pickups_and_effects.py index 601643481..f524e582b 100644 --- a/aimmo-game/tests/functional/test_damage_pickups_and_effects.py +++ b/aimmo-game/tests/functional/test_damage_pickups_and_effects.py @@ -1,9 +1,9 @@ from unittest import TestCase from hypothesis import given, assume -import hypothesis.strategies as st +from hypothesis import strategies as st import math -from mock_world import MockWorld +from .mock_world import MockWorld from simulation.location import Location from simulation.pickups import DamageBoostPickup, DAMAGE_BOOST_DEFAULT diff --git a/aimmo-game/tests/functional/test_effect_expiry.py b/aimmo-game/tests/functional/test_effect_expiry.py index 9c9ecd314..37f034453 100644 --- a/aimmo-game/tests/functional/test_effect_expiry.py +++ b/aimmo-game/tests/functional/test_effect_expiry.py @@ -1,6 +1,6 @@ from unittest import TestCase -from mock_world import MockWorld +from .mock_world import MockWorld from simulation.location import Location from simulation.pickups import DamageBoostPickup, InvulnerabilityPickup diff --git a/aimmo-game/tests/functional/test_health_pickups_and_effects.py b/aimmo-game/tests/functional/test_health_pickups_and_effects.py index 76ccdc66e..fdf0e7ef4 100644 --- a/aimmo-game/tests/functional/test_health_pickups_and_effects.py +++ b/aimmo-game/tests/functional/test_health_pickups_and_effects.py @@ -4,7 +4,7 @@ from hypothesis import given import hypothesis.strategies as st -from mock_world import MockWorld +from .mock_world import MockWorld from simulation.location import Location from simulation.pickups import ( diff --git a/aimmo-game/tests/functional/test_invulnerability_pickups_and_effects.py b/aimmo-game/tests/functional/test_invulnerability_pickups_and_effects.py index 32747dfe2..e6b990412 100644 --- a/aimmo-game/tests/functional/test_invulnerability_pickups_and_effects.py +++ b/aimmo-game/tests/functional/test_invulnerability_pickups_and_effects.py @@ -1,6 +1,6 @@ from unittest import TestCase -from mock_world import MockWorld +from .mock_world import MockWorld from simulation.location import Location from simulation.pickups import InvulnerabilityPickup diff --git a/aimmo-game/tests/functional/test_movements_in_map.py b/aimmo-game/tests/functional/test_movements_in_map.py index 43f9eed9b..e1d9ad3d7 100644 --- a/aimmo-game/tests/functional/test_movements_in_map.py +++ b/aimmo-game/tests/functional/test_movements_in_map.py @@ -1,6 +1,6 @@ from unittest import TestCase -from mock_world import MockWorld +from .mock_world import MockWorld from simulation.location import Location from simulation import map_generator from simulation.simulation_runner import ConcurrentSimulationRunner diff --git a/aimmo-game/tests/test_service.py b/aimmo-game/tests/test_service.py index f63df1475..259bf05da 100644 --- a/aimmo-game/tests/test_service.py +++ b/aimmo-game/tests/test_service.py @@ -43,15 +43,6 @@ def setUp(self): test_game_state = GameState(WorldMap(grid, {}), self.avatar_manager) self.world_state_json = test_game_state.serialise() - def test_healthy_flask(self): - """ - Tests the flask service. HEALTHY is returned if the app can be routed to root. - """ - service.flask_app.config['TESTING'] = True - self.app = service.flask_app.test_client() - response = self.app.get('/game-1') - self.assertEqual(response.data, 'HEALTHY') - def test_correct_json_player_dictionary(self): """ Ensures the "players" element of the get_game_state() JSON returns the correct information for the dummy diff --git a/aimmo-game/tests/test_simulation/avatar/test_avatar_wrapper.py b/aimmo-game/tests/test_simulation/avatar/test_avatar_wrapper.py index 06f8a538f..9738eeec8 100644 --- a/aimmo-game/tests/test_simulation/avatar/test_avatar_wrapper.py +++ b/aimmo-game/tests/test_simulation/avatar/test_avatar_wrapper.py @@ -84,7 +84,7 @@ def test_non_existant_action(self): def add_effects(self, num=2): effects = [] - for _ in xrange(num): + for _ in range(num): effect = MockEffect(self.avatar) self.avatar.effects.add(effect) effects.append(effect) diff --git a/aimmo-game/tests/test_simulation/test_cell.py b/aimmo-game/tests/test_simulation/test_cell.py index a4219dc95..d76f96de7 100644 --- a/aimmo-game/tests/test_simulation/test_cell.py +++ b/aimmo-game/tests/test_simulation/test_cell.py @@ -1,7 +1,7 @@ from unittest import TestCase from simulation.cell import Cell -from tests.test_simulation.test_world_map import Serialiser +from .test_world_map import Serialiser class TestCell(TestCase): diff --git a/aimmo-game/tests/test_simulation/test_direction.py b/aimmo-game/tests/test_simulation/test_direction.py index 4093bd71c..ff38c2bfc 100644 --- a/aimmo-game/tests/test_simulation/test_direction.py +++ b/aimmo-game/tests/test_simulation/test_direction.py @@ -34,8 +34,8 @@ def test_too_far(self): def test_repr(self): txt = repr(Direction(1, 0)) - self.assertRegexpMatches(txt, 'x *= *1') - self.assertRegexpMatches(txt, 'y *= *0') + self.assertRegex(txt, 'x *= *1') + self.assertRegex(txt, 'y *= *0') def test_incorrect_equality(self): d1 = Direction(0, 1) diff --git a/aimmo-game/tests/test_simulation/test_game_runner.py b/aimmo-game/tests/test_simulation/test_game_runner.py index 4387ca23a..e1e70f31a 100644 --- a/aimmo-game/tests/test_simulation/test_game_runner.py +++ b/aimmo-game/tests/test_simulation/test_game_runner.py @@ -1,6 +1,7 @@ from unittest import TestCase from json import dumps - +import asyncio +import pytest import mock from .mock_communicator import MockCommunicator @@ -39,69 +40,83 @@ def __call__(self, url, request): return dumps(self.value) -class TestGameRunner(TestCase): - def setUp(self): - game_state = GameState(InfiniteMap(), AvatarManager()) - self.game_runner = GameRunner(worker_manager_class=ConcreteWorkerManager, - game_state_generator=lambda avatar_manager: game_state, - port='0000', - django_api_url='http://test') - self.game_runner.communicator = MockCommunicator() - - def test_correct_url(self): - self.game_runner.communicator.get_game_metadata = mock.MagicMock() - self.game_runner.update() - # noinspection PyUnresolvedReferences - self.game_runner.communicator.get_game_metadata.assert_called_once() - - def test_workers_and_avatars_added(self): - self.game_runner.communicator.data = RequestMock(3).value - self.game_runner.update() - - self.assertEqual(len(self.game_runner.worker_manager.final_workers), 3) - for i in range(3): - self.assertIn(i, self.game_runner.game_state.avatar_manager.avatars_by_id) - self.assertIn(i, self.game_runner.worker_manager.final_workers) - self.assertEqual(self.game_runner.worker_manager.get_code(i), 'code for %s' % i) - - def test_changed_code(self): - self.game_runner.communicator.data = RequestMock(4).value - self.game_runner.update() - self.game_runner.communicator.change_code(0, 'changed 0') - self.game_runner.communicator.change_code(2, 'changed 2') - self.game_runner.update() - - for i in range(4): - self.assertIn(i, self.game_runner.worker_manager.final_workers) - self.assertIn(i, self.game_runner.game_state.avatar_manager.avatars_by_id) - - for i in (1, 3): - self.assertEqual(self.game_runner.worker_manager.get_code(i), 'code for %s' % i) - - for i in (0, 2): - self.assertIn(i, self.game_runner.worker_manager.updated_workers) - self.assertEqual(self.game_runner.worker_manager.get_code(i), 'changed %s' % i) - - def test_logs_cleared_at_each_update(self): - self.game_runner.communicator.data = RequestMock(3).value - self.game_runner.update_workers() - first_worker = self.game_runner.worker_manager.player_id_to_worker[0] - first_worker.log = 'test logs' - - self.game_runner.worker_manager.clear_logs() - - self.assertIsNone(first_worker.log) - - def test_remove_avatars(self): - self.game_runner.communicator.data = RequestMock(3).value - self.game_runner.update() - del self.game_runner.communicator.data['main']['users'][1] - self.game_runner.update() - - for i in range(3): - if i == 1: - self.assertNotIn(i, self.game_runner.worker_manager.final_workers) - self.assertNotIn(i, self.game_runner.game_state.avatar_manager.avatars_by_id) - else: - self.assertIn(i, self.game_runner.worker_manager.final_workers) - self.assertIn(i, self.game_runner.game_state.avatar_manager.avatars_by_id) +@pytest.fixture +def game_runner(): + async def mock_callback(): + pass + game_state = GameState(InfiniteMap(), AvatarManager()) + game_runner = GameRunner(worker_manager_class=ConcreteWorkerManager, + game_state_generator=lambda avatar_manager: game_state, + port='0000', + django_api_url='http://test') + + game_runner.communicator = MockCommunicator() + game_runner.set_end_turn_callback(mock_callback) + return game_runner + + +@pytest.mark.asyncio +async def test_correct_url(game_runner): + game_runner.communicator.get_game_metadata = mock.MagicMock() + await game_runner.update() + # noinspection PyUnresolvedReferences + game_runner.communicator.get_game_metadata.assert_called_once() + + +@pytest.mark.asyncio +async def test_workers_and_avatars_added(game_runner): + game_runner.communicator.data = RequestMock(3).value + await game_runner.update() + + assert len(game_runner.worker_manager.final_workers) == 3 + for i in range(3): + assert i in game_runner.game_state.avatar_manager.avatars_by_id + assert i in game_runner.worker_manager.final_workers + assert game_runner.worker_manager.get_code(i) == 'code for %s' % i + + +@pytest.mark.asyncio +async def test_changed_code(game_runner): + game_runner.communicator.data = RequestMock(4).value + await game_runner.update() + game_runner.communicator.change_code(0, 'changed 0') + game_runner.communicator.change_code(2, 'changed 2') + await game_runner.update() + + for i in range(4): + assert i in game_runner.worker_manager.final_workers + assert i in game_runner.game_state.avatar_manager.avatars_by_id + + for i in (1, 3): + assert game_runner.worker_manager.get_code(i) in 'code for %s' % i + + for i in (0, 2): + assert i in game_runner.worker_manager.updated_workers + assert game_runner.worker_manager.get_code(i) in 'changed %s' % i + + +def test_logs_cleared_at_each_update(game_runner): + game_runner.communicator.data = RequestMock(3).value + game_runner.update_workers() + first_worker = game_runner.worker_manager.player_id_to_worker[0] + first_worker.log = 'test logs' + + game_runner.worker_manager.clear_logs() + + assert first_worker.log is None + + +@pytest.mark.asyncio +async def test_remove_avatars(game_runner): + game_runner.communicator.data = RequestMock(3).value + await game_runner.update() + del game_runner.communicator.data['main']['users'][1] + await game_runner.update() + + for i in range(3): + if i == 1: + assert i not in game_runner.worker_manager.final_workers + assert i not in game_runner.game_state.avatar_manager.avatars_by_id + else: + assert i in game_runner.worker_manager.final_workers + assert i in game_runner.game_state.avatar_manager.avatars_by_id diff --git a/aimmo-game/tests/test_simulation/test_map_generator.py b/aimmo-game/tests/test_simulation/test_map_generator.py index 636f6c17e..95d8534f9 100644 --- a/aimmo-game/tests/test_simulation/test_map_generator.py +++ b/aimmo-game/tests/test_simulation/test_map_generator.py @@ -3,6 +3,7 @@ import random import unittest +from simulation import map_generator from simulation import map_generator from simulation.location import Location from simulation.map_generator import get_random_edge_index @@ -37,10 +38,10 @@ def test_get_random_edge_index(self): (2, 0), get_random_edge_index(map, rng=ConstantRng(5))) # Verify no out of bounds - with self.assertRaisesRegexp(ValueError, 'Beyond range'): + with self.assertRaisesRegex(ValueError, 'Beyond range'): get_random_edge_index(map, rng=ConstantRng(-1)) - with self.assertRaisesRegexp(ValueError, 'Beyond range'): + with self.assertRaisesRegex(ValueError, 'Beyond range'): get_random_edge_index(map, rng=ConstantRng(6)) def test_get_random_edge_index_can_give_all_possible(self): @@ -57,10 +58,10 @@ def test_get_random_edge_index_can_give_all_possible(self): def test_out_of_bounds_random_edge(self): map = WorldMap.generate_empty_map(3, 4, {}) - with self.assertRaisesRegexp(ValueError, 'Beyond range'): + with self.assertRaisesRegex(ValueError, 'Beyond range'): get_random_edge_index(map, rng=ConstantRng(-1)) - with self.assertRaisesRegexp(ValueError, 'Beyond range'): + with self.assertRaisesRegex(ValueError, 'Beyond range'): get_random_edge_index(map, rng=ConstantRng(6)) @@ -114,6 +115,9 @@ def test_map_contains_some_habitable_cell_on_border(self): habitable_edge_cells = [cell for cell in edge_cells if cell.habitable] self.assertGreaterEqual(len(habitable_edge_cells), 1) + + def test_shortest_path(self): + m = self.get_map(START_WIDTH=4) def test_not_complete(self): game_state = self.get_game_state() diff --git a/aimmo-game/tests/test_simulation/worker_managers/test_local_worker_manager.py b/aimmo-game/tests/test_simulation/worker_managers/test_local_worker_manager.py index 26242a8db..b632b2968 100644 --- a/aimmo-game/tests/test_simulation/worker_managers/test_local_worker_manager.py +++ b/aimmo-game/tests/test_simulation/worker_managers/test_local_worker_manager.py @@ -2,7 +2,7 @@ import mock from unittest import TestCase -from urlparse import urlparse +from urllib.parse import urlparse from simulation.worker_managers.local_worker_manager import LocalWorkerManager @@ -20,8 +20,8 @@ def test_local_worker_ports_do_not_conflict(self, docker_from_env): local_worker2 = worker_manager2.create_worker(1) url2 = urlparse(local_worker2) - self.assertEquals(url1.port, 11989) - self.assertEquals(url2.port, 21989) + self.assertEqual(url1.port, 11989) + self.assertEqual(url2.port, 21989) @mock.patch('docker.from_env') def test_local_workers_in_the_same_game_do_not_have_port_conflicts(self, docker_from_env): @@ -32,5 +32,5 @@ def test_local_workers_in_the_same_game_do_not_have_port_conflicts(self, docker_ url1 = urlparse(local_worker1) url2 = urlparse(local_worker2) - self.assertEquals(url1.port, 11989) - self.assertEquals(url2.port, 11990) + self.assertEqual(url1.port, 11989) + self.assertEqual(url2.port, 11990) diff --git a/aimmo-game/tests/test_socketio.py b/aimmo-game/tests/test_socketio.py index 76ef0f422..6b324c30c 100644 --- a/aimmo-game/tests/test_socketio.py +++ b/aimmo-game/tests/test_socketio.py @@ -1,8 +1,10 @@ -from unittest import TestCase +from unittest import TestCase, mock import random import string -import mock import os +import asyncio +import pytest +from asynctest import CoroutineMock import service from simulation.worker_managers.local_worker_manager import LocalWorkerManager @@ -24,10 +26,9 @@ def wrapper(*args, **kwargs): return wrapper return decorator +class TestSocketIO: -class TestSocketIO(TestCase): - - def setUp(self): + def setup_method(self, method): os.environ['GAME_ID'] = '1' self.environ = {'QUERY_STRING': 'avatar_id=1&EIO=3&transport=polling&t=MJhoMgb'} self.game_api = self.create_game_api() @@ -37,12 +38,12 @@ def setUp(self): string.digits) for _ in range(19)) - def tearDown(self): + def teardown_method(self, method): del os.environ['GAME_ID'] @mock.patch('docker.from_env') - @mock.patch('service.flask_app') - def create_game_api(self, flask_app, docker_from_env): + @mock.patch('service.app') + def create_game_api(self, app, docker_from_env): game_runner = GameRunner(worker_manager_class=LocalWorkerManager, game_state_generator=lambda avatar_manager: MockGameState(), django_api_url='http://test', @@ -50,80 +51,93 @@ def create_game_api(self, flask_app, docker_from_env): return service.GameAPI(game_state=game_runner.game_state, worker_manager=game_runner.worker_manager) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_socketio_emit_called(self, mocked_socketio, flask_app): + async def test_socketio_emit_called(self, mocked_socketio, app): self.game_api.worker_manager.add_new_worker(1) - self.game_api.register_world_update_on_connect()(self.sid, self.environ) + await self.game_api.register_world_update_on_connect()(self.sid, self.environ) - self.assertTrue(mocked_socketio.return_value.emit.assert_called_once) + assert mocked_socketio.manager.emit.mockreturn_value.emit.assert_called_once - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') + @mock.patch('service.socketio_server.emit', new_callable=CoroutineMock()) @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_matched_session_id_to_avatar_id_mapping(self, mocked_socketio, flask_app): - self.assertEqual(len(self.mocked_mappings), 0) + async def test_matched_session_id_to_avatar_id_mapping(self, mocked_socketio, mocked_emit, app): + assert len(self.mocked_mappings) == 0 self.game_api.worker_manager.add_new_worker(1) - self.game_api.register_world_update_on_connect()(self.sid, self.environ) + await self.game_api.register_world_update_on_connect()(self.sid, self.environ) - self.assertEqual(len(self.mocked_mappings), 1) - self.assertTrue(self.sid in self.mocked_mappings) - self.assertEqual(int(self.mocked_mappings[self.sid]), 1) + assert len(self.mocked_mappings) == 1 + assert self.sid in self.mocked_mappings + assert int(self.mocked_mappings[self.sid]) == 1 - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_no_match_session_id_to_avatar_id_mapping(self, mocked_socketio, flask_app): + async def test_no_match_session_id_to_avatar_id_mapping(self, mocked_socketio, app): self.environ['QUERY_STRING'] = 'corrupted!@$%string123' - self.assertEqual(len(self.mocked_mappings), 0) + assert len(self.mocked_mappings) == 0 self.game_api.worker_manager.add_new_worker(1) - self.game_api.register_world_update_on_connect()(self.sid, self.environ) + await self.game_api.register_world_update_on_connect()(self.sid, self.environ) - self.assertEqual(len(self.mocked_mappings), 0) - self.assertFalse(self.sid in self.mocked_mappings) + assert len(self.mocked_mappings) == 0 + assert not self.sid in self.mocked_mappings - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_send_updates_for_one_user(self, mocked_socketio, flask_app): + async def test_send_updates_for_one_user(self, mocked_socketio, app): self.mocked_mappings[self.sid] = 1 self.game_api.worker_manager.add_new_worker(self.mocked_mappings[self.sid]) worker = self.game_api.worker_manager.player_id_to_worker[self.mocked_mappings[self.sid]] worker.log = 'Logs one' - self.game_api.send_updates() + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() - game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) - log_call = mock.call('log', 'Logs one', room=self.sid) + game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) + log_call = mock.call('log', 'Logs one', room=self.sid) - mocked_socketio.emit.assert_has_calls([game_state_call, log_call], any_order=True) + mocked_emit.assert_has_calls([game_state_call, log_call], any_order=True) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_no_logs_not_emitted(self, mocked_socketio, flask_app): + async def test_no_logs_not_emitted(self, mocked_socketio, app): """ If there are no logs for an avatar, no logs should be emitted. """ self.mocked_mappings[self.sid] = 1 self.game_api.worker_manager.add_new_worker(self.mocked_mappings[self.sid]) - self.game_api.send_updates() - mocked_socketio.emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() + + mocked_emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_empty_logs_not_emitted(self, mocked_socketio, flask_app): + async def test_empty_logs_not_emitted(self, mocked_socketio, app): """ If the logs are an empty sting, no logs should be emitted. """ self.mocked_mappings[self.sid] = 1 self.game_api.worker_manager.add_new_worker(self.mocked_mappings[self.sid]) worker = self.game_api.worker_manager.player_id_to_worker[self.mocked_mappings[self.sid]] worker.logs = '' - self.game_api.send_updates() + + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() - mocked_socketio.emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) + mocked_emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_send_updates_for_multiple_users(self, mocked_socketio, flask_app): + async def test_send_updates_for_multiple_users(self, mocked_socketio, app): self.mocked_mappings[self.sid] = 1 self.mocked_mappings['differentsid'] = 2 @@ -134,50 +148,58 @@ def test_send_updates_for_multiple_users(self, mocked_socketio, flask_app): worker_one.log = 'Logs one' worker_two.log = 'Logs two' - self.game_api.send_updates() + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() - user_one_game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) - user_two_game_state_call = mock.call('game-state', {'foo': 'bar'}, room='differentsid') - user_one_log_call = mock.call('log', 'Logs one', room=self.sid) - user_two_log_call = mock.call('log', 'Logs two', room='differentsid') + user_one_game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) + user_two_game_state_call = mock.call('game-state', {'foo': 'bar'}, room='differentsid') + user_one_log_call = mock.call('log', 'Logs one', room=self.sid) + user_two_log_call = mock.call('log', 'Logs two', room='differentsid') - mocked_socketio.emit.assert_has_calls([user_one_game_state_call, - user_two_game_state_call, - user_one_log_call, - user_two_log_call], any_order=True) + mocked_emit.assert_has_calls([user_one_game_state_call, + user_two_game_state_call, + user_one_log_call, + user_two_log_call], any_order=True) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_send_code_changed_flag(self, mocked_socketio, flask_app): + async def test_send_code_changed_flag(self, mocked_socketio, app): self.mocked_mappings[self.sid] = 1 self.game_api.worker_manager.add_new_worker(self.mocked_mappings[self.sid]) worker = self.game_api.worker_manager.player_id_to_worker[self.mocked_mappings[self.sid]] worker.has_code_updated = True - self.game_api.send_updates() + + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() - user_game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) - user_game_code_changed_call = mock.call('feedback-avatar-updated', room=self.sid) + user_game_state_call = mock.call('game-state', {'foo': 'bar'}, room=self.sid) + user_game_code_changed_call = mock.call('feedback-avatar-updated', room=self.sid) - mocked_socketio.emit.assert_has_calls([user_game_state_call, user_game_code_changed_call], any_order=True) + mocked_emit.assert_has_calls([user_game_state_call, user_game_code_changed_call], any_order=True) - @mock.patch('service.flask_app') + @pytest.mark.asyncio + @mock.patch('service.app') @mock.patch('service.socketio_server', new_callable=MockedSocketIOServer) - def test_send_false_flag_not_sent(self, mocked_socketio, flask_app): + async def test_send_false_flag_not_sent(self, mocked_socketio, app): self.mocked_mappings[self.sid] = 1 self.game_api.worker_manager.add_new_worker(self.mocked_mappings[self.sid]) worker = self.game_api.worker_manager.player_id_to_worker[self.mocked_mappings[self.sid]] worker.has_code_updated = False - self.game_api.send_updates() - mocked_socketio.emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) + with mock.patch('service.socketio_server.emit', new=CoroutineMock()) as mocked_emit: + await self.game_api.send_updates() + + mocked_emit.assert_called_once_with('game-state', {'foo': 'bar'}, room=self.sid) - def test_remove_session_id_on_disconnect(self): + @pytest.mark.asyncio + async def test_remove_session_id_on_disconnect(self): self.mocked_mappings[self.sid] = 1 - self.assertEqual(len(self.mocked_mappings), 1) - self.assertTrue(self.sid in self.mocked_mappings) - self.assertEqual(self.mocked_mappings[self.sid], 1) + assert len(self.mocked_mappings) == 1 + assert self.sid in self.mocked_mappings + assert self.mocked_mappings[self.sid] == 1 - self.game_api.register_remove_session_id_from_mappings()(sid=self.sid) + await self.game_api.register_remove_session_id_from_mappings()(sid=self.sid) - self.assertFalse(self.sid in self.mocked_mappings) - self.assertEqual(len(self.mocked_mappings), 0) + assert self.sid not in self.mocked_mappings + assert len(self.mocked_mappings) == 0 diff --git a/aimmo/models.py b/aimmo/models.py index 3a1adb0af..b8e510bf3 100644 --- a/aimmo/models.py +++ b/aimmo/models.py @@ -60,15 +60,15 @@ def can_user_play(self, user): def settings_as_dict(self): return { - 'TARGET_NUM_CELLS_PER_AVATAR': self.target_num_cells_per_avatar, - 'TARGET_NUM_SCORE_LOCATIONS_PER_AVATAR': self.target_num_score_locations_per_avatar, - 'SCORE_DESPAWN_CHANCE': self.score_despawn_chance, - 'TARGET_NUM_PICKUPS_PER_AVATAR': self.target_num_pickups_per_avatar, - 'PICKUP_SPAWN_CHANCE': self.pickup_spawn_chance, - 'OBSTACLE_RATIO': self.obstacle_ratio, - 'START_HEIGHT': self.start_height, - 'START_WIDTH': self.start_width, - 'GENERATOR': self.generator, + "TARGET_NUM_CELLS_PER_AVATAR": self.target_num_cells_per_avatar, + "TARGET_NUM_SCORE_LOCATIONS_PER_AVATAR": self.target_num_score_locations_per_avatar, + "SCORE_DESPAWN_CHANCE": self.score_despawn_chance, + "TARGET_NUM_PICKUPS_PER_AVATAR": self.target_num_pickups_per_avatar, + "PICKUP_SPAWN_CHANCE": self.pickup_spawn_chance, + "OBSTACLE_RATIO": self.obstacle_ratio, + "START_HEIGHT": self.start_height, + "START_WIDTH": self.start_width, + "GENERATOR": self.generator, } def save(self, *args, **kwargs): diff --git a/aimmo/views.py b/aimmo/views.py index c912e08cd..4b3f1fc1c 100644 --- a/aimmo/views.py +++ b/aimmo/views.py @@ -1,6 +1,7 @@ import cPickle as pickle import logging import os +import json from django.contrib.auth.decorators import login_required from django.core.exceptions import ValidationError @@ -63,7 +64,7 @@ def list_games(request): game.pk: { 'name': game.name, - 'settings': pickle.dumps(game.settings_as_dict()), + 'settings': json.dumps(game.settings_as_dict()), } for game in Game.objects.exclude_inactive() } return JsonResponse(response) @@ -99,24 +100,13 @@ def connection_parameters(request, game_id): """ env_connection_settings = game_renderer.get_environment_connection_settings(game_id) - try: - avatar_id = game_renderer.get_avatar_id_from_user(user=request.user, game_id=game_id) - except UserCannotPlayGameException: - LOGGER.warning('HTTP 401 returned. User {} unauthorised to play.'.format(request.user.id)) - return HttpResponse('User unauthorized to play', - status=401) - except Avatar.DoesNotExist: - LOGGER.warning('Avatar does not exist for user {} in game {}'.format(request.user.id, - game_id)) - return HttpResponse('Avatar does not exist for this user', - status=404) - except Exception as e: - LOGGER.error('Unknown error occurred while getting connection parameters!') - LOGGER.error(e) - return HttpResponse('Unknown error occurred when getting the current avatar', - status=500) - env_connection_settings.update({'avatar_id': avatar_id}) - return JsonResponse(env_connection_settings) + avatar_id, response = get_avatar_id(request, game_id) + + if avatar_id: + env_connection_settings.update({'avatar_id': avatar_id}) + return JsonResponse(env_connection_settings) + else: + return response @csrf_exempt @@ -193,25 +183,32 @@ def add_game(request): def current_avatar_in_game(request, game_id): + avatar_id, response = get_avatar_id(request, game_id) + + if avatar_id: + return JsonResponse({'current_avatar_id': avatar_id}) + else: + return response + + +def get_avatar_id(request, game_id): + avatar_id = None + response = None try: avatar_id = game_renderer.get_avatar_id_from_user(user=request.user, game_id=game_id) except UserCannotPlayGameException: LOGGER.warning('HTTP 401 returned. User {} unauthorised to play.'.format(request.user.id)) - return HttpResponse('User unauthorized to play', - status=401) + response = HttpResponse('User unauthorized to play', status=401) except Avatar.DoesNotExist: - LOGGER.warning('Avatar does not exist for user {} in game {}'.format(request.user.id, - game_id)) - return HttpResponse('Avatar does not exist for this user', - status=404) + LOGGER.warning('Avatar does not exist for user {} in game {}'.format(request.user.id, game_id)) + response = HttpResponse('Avatar does not exist for this user', status=404) except Exception as e: LOGGER.error('Unknown error occurred while getting connection parameters!') LOGGER.error(e) - return HttpResponse('Unknown error occurred when getting the current avatar', - status=500) + response = HttpResponse('Unknown error occurred when getting the current avatar', status=500) - return JsonResponse({'current_avatar_id': avatar_id}) + return avatar_id, response def csrfToken(request): diff --git a/aimmo_runner/docker_scripts.py b/aimmo_runner/docker_scripts.py index 4c899b00a..aeed9267b 100644 --- a/aimmo_runner/docker_scripts.py +++ b/aimmo_runner/docker_scripts.py @@ -38,7 +38,7 @@ def create_docker_client(raw_env_settings): ) -def build_docker_images(minikube=None): +def build_docker_images(minikube=None, build_target=None): """ Find environment settings and builds docker images for each directory. @@ -59,7 +59,8 @@ def build_docker_images(minikube=None): client.images.build( path=path, tag=tag, - encoding='gzip' + encoding='gzip', + target=build_target ) diff --git a/aimmo_runner/minikube.py b/aimmo_runner/minikube.py index af3771b99..e984547cd 100644 --- a/aimmo_runner/minikube.py +++ b/aimmo_runner/minikube.py @@ -87,12 +87,14 @@ def delete_components(api_instance, extensions_api_instance): api_instance.delete_namespaced_replication_controller( body=kubernetes.client.V1DeleteOptions(), name=rc.metadata.name, - namespace='default') + namespace='default', + grace_period_seconds=0) for pod in api_instance.list_namespaced_pod('default').items: api_instance.delete_namespaced_pod( body=kubernetes.client.V1DeleteOptions(), name=pod.metadata.name, - namespace='default') + namespace='default', + grace_period_seconds=0) for service in api_instance.list_namespaced_service('default').items: api_instance.delete_namespaced_service( name=service.metadata.name, @@ -133,7 +135,7 @@ def create_roles(): run_command(['kubectl', 'apply', '-Rf', 'rbac']) -def start(): +def start(build_target=None): """ The entry point to the minikube class. Sends calls appropriately to set up minikube. @@ -144,7 +146,7 @@ def start(): os.environ['MINIKUBE_PATH'] = MINIKUBE_EXECUTABLE start_cluster(MINIKUBE_EXECUTABLE) create_roles() - build_docker_images(MINIKUBE_EXECUTABLE) + build_docker_images(MINIKUBE_EXECUTABLE, build_target=build_target) restart_ingress_addon(MINIKUBE_EXECUTABLE) ingress = create_ingress_yaml() game_creator = create_creator_yaml() diff --git a/aimmo_runner/runner.py b/aimmo_runner/runner.py index d07c77c72..be19a7166 100644 --- a/aimmo_runner/runner.py +++ b/aimmo_runner/runner.py @@ -34,7 +34,7 @@ def create_superuser_if_missing(username, password): password=password) -def run(use_minikube, server_wait=True, capture_output=False, test_env=False): +def run(use_minikube, server_wait=True, capture_output=False, test_env=False, build_target=None): logging.basicConfig() if test_env: os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_settings") @@ -62,7 +62,7 @@ def run(use_minikube, server_wait=True, capture_output=False, test_env=False): # Import minikube here, so we can install the dependencies first from aimmo_runner import minikube - minikube.start() + minikube.start(build_target=build_target) server_args.append('0.0.0.0:8000') os.environ['AIMMO_MODE'] = 'minikube' @@ -70,8 +70,11 @@ def run(use_minikube, server_wait=True, capture_output=False, test_env=False): time.sleep(2) os.environ['AIMMO_MODE'] = 'threads' docker_scripts.delete_containers() - docker_scripts.build_docker_images() - docker_scripts.start_game_creator() + if build_target == 'tester': + run_command(['python', 'all_tests.py']) + else: + docker_scripts.build_docker_images(build_target=build_target) + docker_scripts.start_game_creator() os.environ['NODE_ENV'] = 'development' if settings.DEBUG else 'production' server = run_command_async(['python', _MANAGE_PY, 'runserver'] + server_args, capture_output=capture_output) diff --git a/all_tests.py b/all_tests.py index ad80b180b..357aafcac 100755 --- a/all_tests.py +++ b/all_tests.py @@ -8,13 +8,16 @@ -c, --coverage compute the coverage while running tests. """ +from __future__ import print_function import os import subprocess import sys -from aimmo_runner import runner +import docker +from aimmo_runner import runner, docker_scripts + BASE_DIR = os.path.abspath(os.path.dirname(__file__)) -APPS = ('aimmo/', 'integration_tests/', 'aimmo-game-worker/', 'aimmo-game-creator/', 'aimmo-game/') +APPS = ('aimmo/', 'integration_tests/') def print_help(): @@ -27,12 +30,25 @@ def main(): sys.exit(0) else: compute_coverage = '--coverage' in sys.argv or '-c' in sys.argv + use_docker = '--no-docker-container-tests' not in sys.argv runner.run_command(['pip', 'install', '-e', BASE_DIR]) - sys.exit(run_tests(compute_coverage)) + sys.exit(run_tests(compute_coverage, use_docker=use_docker)) -def run_tests(compute_coverage): +def run_tests(compute_coverage, use_docker=True): failed_apps = [] + if use_docker: + docker_scripts.delete_containers() + client = docker.from_env() + if compute_coverage: + docker_scripts.build_docker_images(build_target='coverage_tester') + else: + docker_scripts.build_docker_images(build_target='tester') + print('Docker containers built, running tests now...') + run_game_creator_tests(client) + run_game_tests(client) + run_worker_tests(client) + docker_scripts.delete_containers() for app in APPS: print('Testing {}'.format(app)) @@ -43,7 +59,7 @@ def run_tests(compute_coverage): else: result = subprocess.call([sys.executable, 'setup.py', 'test'], cwd=dir) if result != 0: - print('Tests failed: '.format(result)) + print('Tests failed: {}'.format(result)) failed_apps.append(app) if compute_coverage: @@ -58,5 +74,35 @@ def run_tests(compute_coverage): return 0 +def run_game_creator_tests(client): + logs = client.containers.run( + name='aimmo-game-creator-tester', + image='ocadotechnology/aimmo-game-creator:test', + stream=True + ) + for log in logs: + print(log, end='') + + +def run_game_tests(client): + logs = client.containers.run( + name='aimmo-game-tester', + image='ocadotechnology/aimmo-game:test', + stream=True + ) + for log in logs: + print(log, end='') + + +def run_worker_tests(client): + logs = client.containers.run( + name='aimmo-worker-tester', + image='ocadotechnology/aimmo-game-worker:test', + stream=True + ) + for log in logs: + print(log, end='') + + if __name__ == '__main__': main() diff --git a/docs/architecture/games/worker-manager.md b/docs/architecture/games/worker-manager.md index 9d8d90e38..95e797db8 100644 --- a/docs/architecture/games/worker-manager.md +++ b/docs/architecture/games/worker-manager.md @@ -3,25 +3,25 @@ --- The worker manager has the following responsibilities: + * Creating new workers * Deleting old workers * Updating worker code -Note that there is a difference between avatars (represented +Note that there is a difference between avatars (represented in the game as `AvatarWrapper`) and a worker. -The first is a representation in the simulation only, while -the latter represents the thread / pod. It is important that -`WorkerManager` has no interaction with simulation logic. - +The first is a representation in the simulation only, while +the latter represents the thread / pod. It is important that +`WorkerManager` has no interaction with simulation logic. -Adding workers is done in a concurrent manner, using a pool -of [green threads](green-threads). This is because the -creation of workers often involves expensive io. Methods -in `WorkerManager` (and its subclasses) **must** be thread safe. +Adding workers is done in a concurrent manner, using a +ThreadPoolExecutor. This is because the creation of +workers often involves expensive io. Methods in +`WorkerManager` (and its subclasses) **must** be thread safe. -Removing and adding workers is done in two different ways -locally and on the cloud. Locally, we use threads, each -running on different pods. With Kubernetes, we create and -remove pods manually. +Removing and adding workers is done in two different ways +locally and on the cloud. Locally, we use threads, each +running on different pods. With Kubernetes, we create and +remove pods manually. -[green-threads]: https://en.wikipedia.org/wiki/Green_threads +[Thread pools]: https://docs.python.org/3/library/concurrent.futures.html diff --git a/integration_tests/setup.py b/integration_tests/setup.py index d79e67088..7ef055f2a 100644 --- a/integration_tests/setup.py +++ b/integration_tests/setup.py @@ -12,6 +12,7 @@ tests_require=[ 'requests', 'psutil', + 'mock', 'kubernetes == 5.0.0', ], test_suite='test_utils.test_suite.DjangoAutoTestSuite', diff --git a/integration_tests/tests/test_integration.py b/integration_tests/tests/test_integration.py index c36f4e7eb..cf811fde6 100644 --- a/integration_tests/tests/test_integration.py +++ b/integration_tests/tests/test_integration.py @@ -49,7 +49,7 @@ def test_superuser_authentication(self, docker_from_env): self.processes = runner.run(use_minikube=False, server_wait=False, capture_output=True, test_env=True) client = Client() response = client.get(reverse(url_string)) - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) csrf_token = response.context['csrf_token'] login_info = { @@ -59,4 +59,4 @@ def test_superuser_authentication(self, docker_from_env): } response = client.post(reverse(url_string), login_info) - self.assertEquals(response.status_code, 302) + self.assertEqual(response.status_code, 302) diff --git a/integration_tests/tests/test_kubernetes.py b/integration_tests/tests/test_kubernetes.py index 95c91943f..c45df7850 100644 --- a/integration_tests/tests/test_kubernetes.py +++ b/integration_tests/tests/test_kubernetes.py @@ -108,7 +108,7 @@ def test_correct_initial_ingress_yaml(self): and only one specific rule, with only one path specified! """ api_response = self.api_extension_instance.list_namespaced_ingress("default") - self.assertEquals(len(api_response.items), 1) + self.assertEqual(len(api_response.items), 1) # NAME self.assertEqual(api_response.items[0].metadata.name, "aimmo-ingress") diff --git a/rbac/game_creator_role.yaml b/rbac/game_creator_role.yaml index 022a4d173..6bd40a8fd 100644 --- a/rbac/game_creator_role.yaml +++ b/rbac/game_creator_role.yaml @@ -13,9 +13,9 @@ rules: - apiGroups: [""] # "" indicates the core API group resources: ["replicationcontrollers", "pods", "services"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] -- apiGroups: [""] +- apiGroups: ["extensions"] resources: ["ingresses"] - verbs: ["patch"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] --- diff --git a/run.py b/run.py index fae699132..553aea894 100755 --- a/run.py +++ b/run.py @@ -4,11 +4,29 @@ import sys import time import traceback +import argparse from aimmo_runner import runner +parser = argparse.ArgumentParser(description='Runs AI:MMO.') + +parser.add_argument('-k', '--kube', dest='use_minikube', + action='store_true', default=False, + help='To specify if you want to use your minikube enviroment for AI:MMO, default is basic docker containers.') +parser.add_argument('-t', '--target', dest='build_target', choices=['runner', 'tester'], + action='store', default='runner', + help='''Specify the build stage you wish the docker containers to stop at. + By default we simply run the project. This can be used to run the tests + but it is recommended that you used 'all_tests.py' + Options: runner, tester ''') + if __name__ == '__main__': try: - runner.run('--kube' in sys.argv or '-k' in sys.argv) + args = parser.parse_args() + + runner.run( + args.use_minikube, + build_target=args.build_target + ) except Exception as err: traceback.print_exc() raise diff --git a/version.txt b/version.txt index 60a2d3e96..44bb5d1f7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.0 \ No newline at end of file +0.4.1 \ No newline at end of file