new tests added, dead host detection/mitigation, fix bug in specs difference computing code

This commit is contained in:
ahmadbilalkhalid 2019-07-18 18:46:49 +05:00
parent 5f4b7088ff
commit f078b9b245
8 changed files with 450 additions and 138 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
.vscode .vscode
__pycache__/ __pycache__/
venv/ venv/
log.txt

View file

@ -11,6 +11,8 @@ black = "==19.3b0"
[packages] [packages]
etcd3 = "*" etcd3 = "*"
python-decouple = "*" python-decouple = "*"
pytest = "*"
coverage = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"

239
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "38090ff76faaefba3892389e2d49d44cdb57752738163a27c99193e960d550e1" "sha256": "8a70e21524bd2bfc041ea7520f90d4a0e981a6c888623f62b76c9df05e9028e1"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -16,6 +16,57 @@
] ]
}, },
"default": { "default": {
"atomicwrites": {
"hashes": [
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.3.0"
},
"attrs": {
"hashes": [
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==19.1.0"
},
"coverage": {
"hashes": [
"sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9",
"sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74",
"sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390",
"sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8",
"sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe",
"sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf",
"sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e",
"sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741",
"sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09",
"sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd",
"sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034",
"sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420",
"sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c",
"sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab",
"sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba",
"sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e",
"sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609",
"sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2",
"sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49",
"sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b",
"sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d",
"sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce",
"sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9",
"sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4",
"sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773",
"sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723",
"sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c",
"sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f",
"sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1",
"sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260",
"sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"
],
"index": "pypi",
"version": "==4.5.3"
},
"etcd3": { "etcd3": {
"hashes": [ "hashes": [
"sha256:25a524b9f032c6631ff0097532907dea81243eaa63c3744510fd1598cc4e0e87" "sha256:25a524b9f032c6631ff0097532907dea81243eaa63c3744510fd1598cc4e0e87"
@ -25,63 +76,113 @@
}, },
"grpcio": { "grpcio": {
"hashes": [ "hashes": [
"sha256:0232add03144dd3cf9b660e2718244cb8e175370dca4d3855cb4e489a7811b53", "sha256:03b78b4e7dcdfe3e257bb528cc93923f9cbbab6d5babf15a60d21e9a4a70b1a2",
"sha256:0f20e6dcb1b8662cdca033bb97c0a8116a5343e3ebc7f71c5fe7f89039978350", "sha256:1ce0ccfbdfe84387dbcbf44adb4ae16ec7ae70e166ffab478993eb1ea1cba3ce",
"sha256:10b07a623d33d4966f45c85d410bc6a79c5ac6341f06c3beda6c22be12cbfe07", "sha256:22e167a9406d73dd19ffe8ed6a485f17e6eac82505be8c108897f15e68badcbb",
"sha256:10c0476d5a52d21f402fc073745dc43b87cc8e080a1f49bbff4e1059019310fb", "sha256:31d0aeca8d8ee2301c62c5c340e0889d653b1280d68f9fa203982cb6337b050e",
"sha256:289dae0b35c59d191c524e976dd0a6f8c995d2062e72621eb866ad0f4472a635", "sha256:44c7f99ca17ebbcc96fc54ed00b454d8313f1eac28c563098d8b901025aff941",
"sha256:2be726f16142d358a0df1e81d583d6820ee561a7856a79cca2fbe49989308be7", "sha256:5471444f53f9db6a1f1f11f5dbc173228881df8446380b6b98f90afb8fd8348e",
"sha256:4338d2a81f5b4ca022e085040b3cfce19419a5ce44aa7e6810ac1df05365bed7", "sha256:561bca3b1bde6d6564306eb05848fd155136e9c3a25d2961129b1e2edba22fce",
"sha256:4c535b46f20e66bee3097583231977e721acdfcb1671d1490c99b7be8902ce18", "sha256:5bf58e1d2c2f55365c06e8cb5abe067b88ca2e5550fb62009c41df4b54505acf",
"sha256:557154aef70a0e979700cc9528bc8b606b668084a29a0d57dbc4b06b078a2f1c", "sha256:6b7163d1e85d76b0815df63fcc310daec02b44532bb433f743142d4febcb181f",
"sha256:5bfdd7e6647498f979dc46583723c852d97b25afe995d55aa1c76a5f9816bc1f", "sha256:766d79cddad95f5f6020037fe60ea8b98578afdf0c59d5a60c106c1bdd886303",
"sha256:87d8943ae7aa6ca5bbad732867d7f17d2550e4966a0c15b52088e8b579422e47", "sha256:770b7372d5ca68308ff66d7baee53369fa5ce985f84bcb6aa1948c1f2f7b02f2",
"sha256:89d8719d8de4d137678f7caa979e1b0a6fd4026f8096ceef8c2d164bbabefaf2", "sha256:7ab178da777fc0f55b6aef5a755f99726e8e4b75e3903954df07b27059b54fcf",
"sha256:9c3f4af989ce860710ac1864dc2e867dd87e6cee51a2368df1b253596868e52f", "sha256:8078305e77c2f6649d36b24d8778096413e474d9d7892c6f92cfb589c9d71b2e",
"sha256:9da52c3c728883aee429bb7c315049f50b2139f680cd86bb1165418e4f93a982", "sha256:85600b63a386d860eeaa955e9335e18dd0d7e5477e9214825abf2c2884488369",
"sha256:9e9736659987beab42d18525ed10d21f80a1ba8389eac03425fbfd5684e6bbf0", "sha256:857d9b939ae128be1c0c792eb885c7ff6a386b9dea899ac4b06f4d90a31f9d87",
"sha256:9ebcbb1a054cab362d29d3be571d43d6b9b23302d9fc4b43e5327000da1680a9", "sha256:87a41630c90c179fa5c593400f30a467c498972c702f348d41e19dafeb1d319e",
"sha256:a93e08636623e24c939851e2e0c0140b14f524b2980c9cdc4ea52b70a871c7e0", "sha256:8805d486c6128cc0fcc8ecf16c4095d99a8693a541ef851429ab334e028a4a97",
"sha256:ac322d86d1a079e0a118d544443ee16f320af0062c191b4754c0c6ec2fc79310", "sha256:8d71b7a89c306a41ccc7741fc9409b14f5b86727455c2a1c0c7cfcb0f784e1f2",
"sha256:b1fb101459868f52df6b61e7bb13375e50badf17a160e39fe1d51ae19e53f461", "sha256:9e1b80bd65f8f160880cb4dad7f55697f6d37b2d7f251fc0c2128e811928f369",
"sha256:b39aac96cceac624a23d540473835086a3ffa77c91030189988c073488434493", "sha256:9e290c84a145ae2411ee0ec9913c41cd7500e2e7485fe93632434d84ef4fda67",
"sha256:b65507bc273c6dbf539175a786a344cc0ac78d50e5584f72c6599733f8a3301f", "sha256:9ec9f88b5bc94bd99372f27cdd53af1c92ba06717380b127733b953cfb181174",
"sha256:be5bb6e47417e537c884a2e2ff2e1a8b2c064a998fcfdfcc67528d4e63e7ebaf", "sha256:a0a02a8b4ba6deadf706d5f849539b3685b72b186a3c9ef5d43e8972ed60fb6f",
"sha256:c92de6a28a909c4f460dc1bbbcb50d676cf0b1f40224b222761f73fdd851b522", "sha256:a4059c59519f5940e01a071f74ae2a60ea8f6185b03d22a09d40c7959a36b16b",
"sha256:c9f5962eb7fa7607b20eb0e4f59ed35829bd600fc0eacb626a6db83229a3e445", "sha256:a6e028c2a6da2ebfa2365a5b32531d311fbfec0e3600fc27e901b64f0ff7e54e",
"sha256:d00bdf9c546ed6e649f785c55b05288e8b2dbb6bf2eb74b6c579fa0d591d35bd", "sha256:adcdebf9f8463df4120c427cf6c9aed39258bccd03ed37b6939e7a145d64d6e0",
"sha256:da804b1dd8293bd9d61b1e6ea989c887ba042a808a4fbdd80001cfa059aafed2", "sha256:bdec982610259d07156a58f80b8c3e69be7751a9208bc577b059c5193d087fad",
"sha256:ead6c5aa3e807345913649c3be395aaca2bbb2d225f18b8f31f37eab225508f6", "sha256:cefc4d4251ffb73feb303d4b7e9d6c367cb60f2db16d259ea28b114045f965aa",
"sha256:eb4d81550ce6f826af4ec6e8d98be347fe96291d718bf115c3f254621ae8d98d", "sha256:d4145c8aa6afbac10ad27e408f7ce15992fe89ba5d0b4abca31c0c2729864c03",
"sha256:ef6a18ec8fd32ec81748fe720544ea2fb2d2dc50fd6d06739d5e2eb8f0626a1c", "sha256:da76dc5ad719ee99de5ea28a5629ff92172cbb4a70d8a6ae3a5b7a53c7382ce1",
"sha256:fad42835656e0b6d3b7ffc900598e776722e30f43b7234a48f2576ca30f31a47", "sha256:dde2452c08ef8b6426ccab6b5b6de9f06d836d9937d6870e68153cbf8cb49348",
"sha256:fb98dbfee0d963b49ae5754554028cf62e6bd695f22de16d242ba9d2f0b7339b", "sha256:e3d88091d2539a4868750914a6fe7b9ec50e42b913851fc1b77423b5bd918530",
"sha256:fb9cd9bb8d26dc17c2dd715a46bca3a879ec8283879b164e85863110dc6e3b2a" "sha256:f9c67cfe6278499d7f83559dc6322a8bbb108e307817a3d7acbfea807b3603cc"
], ],
"version": "==1.21.1" "version": "==1.22.0"
},
"importlib-metadata": {
"hashes": [
"sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
"sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
],
"version": "==0.18"
},
"more-itertools": {
"hashes": [
"sha256:3ad685ff8512bf6dc5a8b82ebf73543999b657eded8c11803d9ba6b648986f4d",
"sha256:8bb43d1f51ecef60d81854af61a3a880555a14643691cc4b64a6ee269c78f09a"
],
"version": "==7.1.0"
},
"packaging": {
"hashes": [
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
],
"version": "==19.0"
},
"pluggy": {
"hashes": [
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
],
"version": "==0.12.0"
}, },
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:03f43eac9d5b651f976e91cf46a25b75e5779d98f0f4114b0abfed83376d75f8", "sha256:05c36022fef3c7d3562ac22402965c0c2b9fe8421f459bb377323598996e407f",
"sha256:0c94b21e6de01362f91a86b372555d22a60b59708599ca9d5032ae9fdf8e3538", "sha256:139b7eadcca0a861d60b523cb37d9475505e0dfb07972436b15407c2b968d87e",
"sha256:2d2a9f30f61f4063fadd7fb68a2510a6939b43c0d6ceeec5c4704f22225da28e", "sha256:15f683006cb77fb849b1f561e509b03dd2b7dcc749086b8dd1831090d0ba4740",
"sha256:34a0b05fca061e4abb77dd180209f68d8637115ff319f51e28a6a9382d69853a", "sha256:2ad566b7b7cdd8717c7af1825e19f09e8fef2787b77fcb979588944657679604",
"sha256:358710fd0db25372edcf1150fa691f48376a134a6c69ce29f38f185eea7699e6", "sha256:35cfcf97642ef62108e10a9431c77733ec7eaab8e32fe4653de20403429907cb",
"sha256:41e47198b94c27ba05a08b4a95160656105745c462af574e4bcb0807164065c0", "sha256:387822859ecdd012fdc25ec879f7f487da6e1d5b1ae6115e227e6be208836f71",
"sha256:8c61cc8a76e9d381c665aecc5105fa0f1878cf7db8b5cd17202603bcb386d0fc", "sha256:4df14cbe1e7134afcfdbb9f058949e31c466de27d9b2f7fb4da9e0b67231b538",
"sha256:a6eebc4db759e58fdac02efcd3028b811effac881d8a5bad1996e4e8ee6acb47", "sha256:586c4ca37a7146d4822c700059f150ac3445ce0aef6f3ea258640838bb892dc2",
"sha256:a9c12f7c98093da0a46ba76ec40ace725daa1ac4038c41e4b1466afb5c45bb01", "sha256:58b11e530e954d29ab3180c48dc558a409f705bf16739fd4e0d3e07924ad7add",
"sha256:cb95068492ba0859b8c9e61fa8ba206a83c64e5d0916fb4543700b2e2b214115", "sha256:63c8c98ccb8c95f41c18fb829aeeab21c6249adee4ed75354125bdc44488f30e",
"sha256:cd98476ce7bb4dcd6a7b101f5eecdc073dafea19f311e36eb8fba1a349346277", "sha256:72edcbacd0c73eef507d2ff1af99a6c27df18e66a3ff4351e401182e4de62b03",
"sha256:ce64cfbea18c535176bdaa10ba740c0fc4c6d998a3f511c17bedb0ae4b3b167c", "sha256:83dc8a561b3b954fd7002c690bb83278b8d1742a1e28abba9aaef28b0c8b437d",
"sha256:dcbb59eac73fd454e8f2c5fba9e3d3320fd4707ed6a9d3ea3717924a6f0903ea", "sha256:913171ecc84c2726b86574e40549a0ea619d569657c5a5ff782a3be7d81401a5",
"sha256:dd67f34458ae716029e2a71ede998e9092493b62a519236ca52e3c5202096c87", "sha256:aabb7c741d3416671c3e6fe7c52970a226e6a8274417a97d7d795f953fadef36",
"sha256:e3c96056eb5b7284a20e256cb0bf783c8f36ad82a4ae5434a7b7cd02384144a7", "sha256:b3452bbda12b1cbe2187d416779de07b2ab4c497d83a050e43c344778763721d",
"sha256:f612d584d7a27e2f39e7b17878430a959c1bc09a74ba09db096b468558e5e126", "sha256:c5d5b8d4a9212338297fa1fa44589f69b470c0ba1d38168b432d577176b386a8",
"sha256:f6de8a7d6122297b81566e5bd4df37fd5d62bec14f8f90ebff8ede1c9726cd0a", "sha256:d86ee389c2c4fc3cebabb8ce83a8e97b6b3b5dc727b7419c1ccdc7b6e545a233",
"sha256:fa529d9261682b24c2aaa683667253175c9acebe0a31105394b221090da75832" "sha256:f2db8c754de788ab8be5e108e1e967c774c0942342b4f8aaaf14063889a6cfdc"
], ],
"version": "==3.8.0" "version": "==3.9.0"
},
"py": {
"hashes": [
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
],
"version": "==1.8.0"
},
"pyparsing": {
"hashes": [
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
],
"version": "==2.4.0"
},
"pytest": {
"hashes": [
"sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d",
"sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77"
],
"index": "pypi",
"version": "==5.0.1"
}, },
"python-decouple": { "python-decouple": {
"hashes": [ "hashes": [
@ -103,6 +204,20 @@
"sha256:b87c1934daa0b2ccc7db153c37b8bf91d12f165936ade8628e7b962b92dc7705" "sha256:b87c1934daa0b2ccc7db153c37b8bf91d12f165936ade8628e7b962b92dc7705"
], ],
"version": "==5.0.4" "version": "==5.0.4"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
},
"zipp": {
"hashes": [
"sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a",
"sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"
],
"version": "==0.5.2"
} }
}, },
"develop": { "develop": {
@ -122,11 +237,11 @@
}, },
"bandit": { "bandit": {
"hashes": [ "hashes": [
"sha256:f89adaff792d1f9b72859784c5f7964c6b5a5f32ca0ca458c9643e02d4fdceac", "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952",
"sha256:fa1fee3cb60a3dca89b7a86c0be82af0e830def961728aba9290854fe18c1f90" "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.6.1" "version": "==1.6.2"
}, },
"black": { "black": {
"hashes": [ "hashes": [
@ -152,11 +267,11 @@
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.7.7" "version": "==3.7.8"
}, },
"gitdb2": { "gitdb2": {
"hashes": [ "hashes": [
@ -181,10 +296,10 @@
}, },
"pbr": { "pbr": {
"hashes": [ "hashes": [
"sha256:9181e2a34d80f07a359ff1d0504fad3a47e00e1cf2c475b0aa7dcb030af54c40", "sha256:36ebd78196e8c9588c972f5571230a059ff83783fabbbbedecc07be263ccd7e6",
"sha256:94bdc84da376b3dd5061aa0c3b6faffe943ee2e56fa4ff9bd63e1643932f34fc" "sha256:5a03f59455ad54f01a94c15829b8b70065462b7bd8d5d7e983306b59127fc841"
], ],
"version": "==5.3.1" "version": "==5.4.0"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [

0
__init__ Normal file
View file

@ -1 +1 @@
Subproject commit cb2a416a17d6789e613ba3b9957917770f4211e1 Subproject commit 615a0709186e74ee5c9ae78f385fd0c4c4b3834d

110
main.py
View file

@ -5,13 +5,23 @@
# 3. v3) Introduce a status endpoint of the scheduler - # 3. v3) Introduce a status endpoint of the scheduler -
# maybe expose a prometheus compatible output # maybe expose a prometheus compatible output
import etcd3
import json import json
import argparse import argparse
import logging
from decouple import config from decouple import config
from collections import Counter from collections import Counter
from functools import reduce from functools import reduce
from etcd3_wrapper import Etcd3Wrapper
from datetime import datetime
logging.basicConfig(
level=logging.DEBUG,
filename="log.txt",
filemode="a",
format="%(asctime)s: %(levelname)s - %(message)s",
datefmt="%d-%b-%y %H:%M:%S",
)
class VmPool(object): class VmPool(object):
@ -20,9 +30,7 @@ class VmPool(object):
self.vms = [] self.vms = []
_vms = self.client.get_prefix(vm_prefix) _vms = self.client.get_prefix(vm_prefix)
self.vms = [ self.vms = [(vm.key, json.loads(vm.value)) for vm in _vms]
(vm[1].key.decode("utf-8"), json.loads(vm[0])) for vm in _vms
]
@staticmethod @staticmethod
def by_host(vms, host): def by_host(vms, host):
@ -38,7 +46,7 @@ class VmPool(object):
def accumulated_specs(vms_specs): def accumulated_specs(vms_specs):
if vms_specs == []: if not vms_specs:
return {} return {}
return reduce((lambda x, y: Counter(x) + Counter(y)), vms_specs) return reduce((lambda x, y: Counter(x) + Counter(y)), vms_specs)
@ -51,16 +59,13 @@ def remaining_resources(host_specs, vms_specs):
return remaining return remaining
def get_suitable_host(etcd_client, vm_prefix, host_prefix, vm_specs): def get_suitable_host(etcd_client, vm_prefix, host_prefix, vm_specs):
vm_pool = VmPool(etcd_client, vm_prefix) vm_pool = VmPool(etcd_client, vm_prefix)
hosts = etcd_client.get_prefix(host_prefix) hosts = etcd_client.get_prefix(host_prefix, value_in_json=True)
hosts = filter(lambda h: h.value["status"] == "ALIVE", hosts)
for host in hosts: for host in hosts:
_host_name, host_specs = ( _host_name, host_value = (host.key, host.value)
host[1].key.decode("utf-8"),
json.loads(host[0]),
)
# Get All Virtual Machines # Get All Virtual Machines
vms = vm_pool.vms vms = vm_pool.vms
@ -72,19 +77,19 @@ def get_suitable_host(etcd_client, vm_prefix, host_prefix, vm_specs):
vms = VmPool.except_status(vms, "REQUESTED_NEW") vms = VmPool.except_status(vms, "REQUESTED_NEW")
running_vms_specs = [vm[1]["specs"] for vm in vms] running_vms_specs = [vm[1]["specs"] for vm in vms]
# Accumulate all of their combined specs # Accumulate all of their combined specs
running_vms_accumulated_specs = accumulated_specs(running_vms_specs) running_vms_accumulated_specs = accumulated_specs(running_vms_specs)
print(running_vms_accumulated_specs)
# Find out remaining resources after # Find out remaining resources after
# host_specs - already running vm_specs # host_specs - already running vm_specs
print(host_value)
remaining = remaining_resources( remaining = remaining_resources(
host_specs, running_vms_accumulated_specs host_value["specs"], running_vms_accumulated_specs
) )
print(remaining)
# Find out remaining - new_vm_specs # Find out remaining - new_vm_specs
remaining = remaining_resources(remaining, vm_specs) remaining = remaining_resources(remaining, vm_specs)
# if remaining resources >= 0 return this host_name # if remaining resources >= 0 return this host_name
if all( if all(
map(lambda x: True if remaining[x] >= 0 else False, remaining) map(lambda x: True if remaining[x] >= 0 else False, remaining)
@ -94,31 +99,78 @@ def get_suitable_host(etcd_client, vm_prefix, host_prefix, vm_specs):
return None return None
def main(vm_prefix, host_prefix): def dead_host_detection(hosts):
dead_hosts_keys = []
for host in hosts:
# Bring out your dead! - Monty Python and the Holy Grail
client = etcd3.client( if "status" in host.value and "last_heartbeat" in host.value:
# Don't count that is already buried
if host.value["status"] == "DEAD":
continue
last_heartbeat = datetime.fromisoformat(
host.value["last_heartbeat"]
)
delta = datetime.utcnow() - last_heartbeat
if delta.total_seconds() > 60:
dead_hosts_keys.append(host.key)
else:
dead_hosts_keys.append(host.key)
return dead_hosts_keys
def dead_host_mitigation(client: Etcd3Wrapper, dead_hosts_keys):
for host_key in dead_hosts_keys:
host = client.get(host_key, value_in_json=True)
host.value["status"] = "DEAD"
host.value["last_heartbeat"] = datetime.utcnow().isoformat()
client.put(host.key, host.value, value_in_json=True)
# Find all vms that were hosted on this dead host
all_vms = client.get_prefix(config("VM_PREFIX"), value_in_json=True)
vms_hosted_on_dead_host = filter(
lambda _vm: _vm.value["hostname"] == host_key, all_vms
)
for vm in vms_hosted_on_dead_host:
vm.value["host"] = ""
vm.value["status"] = "REQUESTED_NEW"
client.put(vm.key, vm.value, value_in_json=True)
def main(vm_prefix, host_prefix):
client = Etcd3Wrapper(
host=config("ETCD_HOST"), port=int(config("ETCD_PORT")) host=config("ETCD_HOST"), port=int(config("ETCD_PORT"))
) )
events_iterator, _ = client.watch_prefix(vm_prefix) events_iterator = client.watch_prefix(vm_prefix, timeout=10)
for event in events_iterator: for e in events_iterator:
key = event.key try:
value = event.value e.value = json.loads(e.value)
if not value: except json.JSONDecodeError:
logging.error(f"Invalid JSON {e.value}")
continue continue
value = json.loads(event.value)
print(key, value) logging.debug(e.key, e.value)
if value["status"] == "REQUESTED_NEW": e_status = e.value["status"]
if e_status == "TIMEOUT":
logging.info("Timeout")
hosts = client.get_prefix(host_prefix, value_in_json=True)
dead_hosts = dead_host_detection(hosts)
dead_host_mitigation(client, dead_hosts)
elif e_status == "REQUESTED_NEW":
host_name = get_suitable_host( host_name = get_suitable_host(
client, vm_prefix, host_prefix, value["specs"] client, vm_prefix, host_prefix, e.value["specs"]
) )
if host_name: if host_name:
value["status"] = "SCHEDULED_DEPLOY" e.value["status"] = "SCHEDULED_DEPLOY"
value["hostname"] = host_name e.value["hostname"] = host_name
client.put(key, json.dumps(value)) client.put(e.key, json.dumps(e.value))
else: else:
# email admin # email admin
print("No Resource Left. Emailing admin....") print("No Resource Left. Emailing admin....")

View file

@ -1,53 +1,83 @@
import unittest import unittest
import sys import sys
import etcd3
import json import json
import multiprocessing
import time
sys.path.insert(0, "../") from datetime import datetime
from main import accumulated_specs, remaining_resources, VmPool from os.path import dirname
BASE_DIR = dirname(dirname(__file__))
sys.path.insert(0, BASE_DIR)
from main import (
accumulated_specs,
remaining_resources,
VmPool,
dead_host_detection,
dead_host_mitigation,
main,
)
from etcd3_wrapper import Etcd3Wrapper
class TestFunctions(unittest.TestCase): class TestFunctions(unittest.TestCase):
def setUp(self): @classmethod
self.client = etcd3.client() def setUpClass(cls):
self.host_prefix = "/v1/host" cls.client = Etcd3Wrapper()
self.vm_prefix = "/v1/vm" cls.host_prefix = "/test/host"
cls.vm_prefix = "/test/vm"
# These deletion could also be in # These deletion could also be in
# tearDown() but it is more appropriate here # tearDown() but it is more appropriate here
# as it enable us to check the ETCD store # as it enable us to check the ETCD store
# even after test is run # even after test is run
self.client.delete_prefix(self.host_prefix) cls.client.client.delete_prefix(cls.host_prefix)
self.client.delete_prefix(self.vm_prefix) cls.client.client.delete_prefix(cls.vm_prefix)
cls.create_hosts(cls)
cls.create_vms(cls)
self.create_hosts() cls.p = multiprocessing.Process(
self.create_vms() target=main, args=[cls.vm_prefix, cls.host_prefix]
)
cls.p.start()
@classmethod
def tearDownClass(cls):
cls.p.terminate()
def create_hosts(self): def create_hosts(self):
host1 = """{ host1 = {
"cpu": 32, "cpu": 32,
"ram": 128, "ram": 128,
"hdd": 1024, "hdd": 1024,
"sdd": 0 "sdd": 0,
}""" "status": "ALIVE",
"last_heartbeat": datetime.utcnow().isoformat(),
host2 = """{ }
host2 = {
"cpu": 16, "cpu": 16,
"ram": 64, "ram": 64,
"hdd": 512, "hdd": 512,
"sdd": 0 "sdd": 0,
}""" "status": "ALIVE",
"last_heartbeat": datetime.utcnow().isoformat(),
}
host3 = """{ host3 = {
"cpu": 16, "cpu": 16,
"ram": 32, "ram": 32,
"hdd": 256, "hdd": 256,
"sdd": 256 "sdd": 256,
}""" "status": "ALIVE",
with self.client.lock("lock"): "last_heartbeat": datetime.utcnow().isoformat(),
self.client.put(f"{self.host_prefix}/1", host1) }
self.client.put(f"{self.host_prefix}/2", host2) with self.client.client.lock("lock"):
self.client.put(f"{self.host_prefix}/3", host3) self.client.put(f"{self.host_prefix}/1", host1, value_in_json=True)
self.client.put(f"{self.host_prefix}/2", host2, value_in_json=True)
self.client.put(f"{self.host_prefix}/3", host3, value_in_json=True)
def create_vms(self): def create_vms(self):
vm1 = json.dumps( vm1 = json.dumps(
@ -121,35 +151,64 @@ class TestFunctions(unittest.TestCase):
{"cpu": 8, "ram": 32}, {"cpu": 8, "ram": 32},
] ]
self.assertEqual( self.assertEqual(
accumulated_specs(vms), accumulated_specs(vms), {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10}
{"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10},
) )
def test_remaining_resources(self): def test_remaining_resources(self):
host_specs = {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10} host_specs = {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10}
vms_specs = {"ssd": 10, "cpu": 32, "ram": 12, "hdd": 0} vms_specs = {"ssd": 10, "cpu": 32, "ram": 12, "hdd": 0}
resultant_specs = {"ssd": 0, "cpu": -16, "ram": 36, "hdd": 10} resultant_specs = {"ssd": 0, "cpu": -16, "ram": 36, "hdd": 10}
self.assertEqual( self.assertEqual(remaining_resources(host_specs, vms_specs),
remaining_resources(host_specs, vms_specs), resultant_specs resultant_specs)
)
def test_vmpool(self): def test_vmpool(self):
self.p.join(1)
vm_pool = VmPool(self.client, self.vm_prefix) vm_pool = VmPool(self.client, self.vm_prefix)
print(self.client.get(f"{self.vm_prefix}/1")[1].key)
self.assertEqual( # vm_pool by host
vm_pool.by_host(vm_pool.vms, f"{self.host_prefix}/1"), actual = vm_pool.by_host(vm_pool.vms, f"{self.host_prefix}/3")
[ ground_truth = [
( (
f"{self.vm_prefix}/1", f"{self.vm_prefix}/1",
{ {
"owner": "meow", "owner": "meow",
"specs": {"cpu": 4, "ram": 8, "hdd": 100, "sdd": 256}, "specs": {"cpu": 4, "ram": 8, "hdd": 100, "sdd": 256},
"hostname": f"{self.host_prefix}/1", "hostname": f"{self.host_prefix}/3",
"status": "SCHEDULED_DEPLOY", "status": "SCHEDULED_DEPLOY",
}, },
) )
], ]
) self.assertEqual(actual[0], ground_truth[0])
# vm_pool by status
actual = vm_pool.by_status(vm_pool.vms, "REQUESTED_NEW")
ground_truth = [
(
f"{self.vm_prefix}/7",
{
"owner": "meow",
"specs": {"cpu": 10, "ram": 22, "hdd": 146, "sdd": 0},
"hostname": "",
"status": "REQUESTED_NEW",
},
)
]
self.assertEqual(actual[0], ground_truth[0])
# vm_pool by except status
actual = vm_pool.except_status(vm_pool.vms, "SCHEDULED_DEPLOY")
ground_truth = [
(
f"{self.vm_prefix}/7",
{
"owner": "meow",
"specs": {"cpu": 10, "ram": 22, "hdd": 146, "sdd": 0},
"hostname": "",
"status": "REQUESTED_NEW",
},
)
]
self.assertEqual(actual[0], ground_truth[0])
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -0,0 +1,83 @@
import unittest
import sys
import json
import multiprocessing
import time
from datetime import datetime
from os.path import dirname
BASE_DIR = dirname(dirname(__file__))
sys.path.insert(0, BASE_DIR)
from main import (
accumulated_specs,
remaining_resources,
VmPool,
dead_host_detection,
dead_host_mitigation,
main,
)
from etcd3_wrapper import Etcd3Wrapper
class TestDeadHostMechanism(unittest.TestCase):
def setUp(self):
self.client = Etcd3Wrapper()
self.host_prefix = "/test/host"
self.vm_prefix = "/test/vm"
self.client.client.delete_prefix(self.host_prefix)
self.client.client.delete_prefix(self.vm_prefix)
self.create_hosts()
def create_hosts(self):
host1 = {
"cpu": 32,
"ram": 128,
"hdd": 1024,
"sdd": 0,
"status": "ALIVE",
"last_heartbeat": datetime.utcnow().isoformat(),
}
host2 = {
"cpu": 16,
"ram": 64,
"hdd": 512,
"sdd": 0,
"status": "ALIVE",
"last_heartbeat": datetime(2011, 1, 1).isoformat(),
}
host3 = {"cpu": 16, "ram": 32, "hdd": 256, "sdd": 256}
host4 = {
"cpu": 16,
"ram": 32,
"hdd": 256,
"sdd": 256,
"status": "DEAD",
"last_heartbeat": datetime(2011, 1, 1).isoformat(),
}
with self.client.client.lock("lock"):
self.client.put(f"{self.host_prefix}/1", host1, value_in_json=True)
self.client.put(f"{self.host_prefix}/2", host2, value_in_json=True)
self.client.put(f"{self.host_prefix}/3", host3, value_in_json=True)
self.client.put(f"{self.host_prefix}/4", host4, value_in_json=True)
def test_dead_host_detection(self):
hosts = self.client.get_prefix(self.host_prefix, value_in_json=True)
deads = dead_host_detection(hosts)
self.assertEqual(deads, ["/test/host/2", "/test/host/3"])
return deads
def test_dead_host_mitigation(self):
deads = self.test_dead_host_detection()
dead_host_mitigation(self.client, deads)
hosts = self.client.get_prefix(self.host_prefix, value_in_json=True)
deads = dead_host_detection(hosts)
self.assertEqual(deads, [])
if __name__ == "__main__":
unittest.main()