update kubernetes 1.24 charm pkg
This commit is contained in:
parent
09f0fa784a
commit
31dc321102
Binary file not shown.
|
|
@ -1,601 +0,0 @@
|
||||||
{
|
|
||||||
"layers": [
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "fcdcea4e5de3e1556c24e6704607862d0ba00a56",
|
|
||||||
"url": "layer:options"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "fb767dcf0786d1d5364199bb3b40bdc86518b45b",
|
|
||||||
"url": "layer:basic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "cc5bd3f49b2fa5e6c3ab2336763c313ec8bf083f",
|
|
||||||
"url": "layer:leadership"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "a7d7b6423db37a47611310039e6ed1929c0a2eab",
|
|
||||||
"url": "layer:status"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/stable",
|
|
||||||
"rev": "b93fae0e73bb48074deb0062db204b621caa9f1f",
|
|
||||||
"url": "layer:kubernetes-common"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/stable",
|
|
||||||
"rev": "c63a7c4cee4b66a8e2025167fd341f45ecee8326",
|
|
||||||
"url": "calico"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "44f244cbd08b86bf2b68bd71c3fb34c7c070c382",
|
|
||||||
"url": "interface:etcd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"branch": "refs/heads/main\nrefs/heads/stable",
|
|
||||||
"rev": "3ebfa8c70580aec7d9fcd2be1c74cef3457117f3",
|
|
||||||
"url": "interface:kubernetes-cni"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"signatures": {
|
|
||||||
".build.manifest": [
|
|
||||||
"build",
|
|
||||||
"dynamic",
|
|
||||||
"unchecked"
|
|
||||||
],
|
|
||||||
".github/workflows/main.yml": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"1d3a0271c31a22266801b9ac8a8189c77bd029349572cb84a021632df5a5adca"
|
|
||||||
],
|
|
||||||
".github/workflows/tox.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"daa88372055fbd68c6033f123b8f679fdfb7c618ce0a289f4e5d3e86026eb131"
|
|
||||||
],
|
|
||||||
".gitignore": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"3437c2cd90de443f44766939172b82e750e19fd474df499ffe003bb807e8cef4"
|
|
||||||
],
|
|
||||||
"CONTRIBUTING.md": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"b2b1873d8401fee3127bce313020dbbc56887d9aa98985e3c4c925490c3f0411"
|
|
||||||
],
|
|
||||||
"DEVELOPING.md": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"b85e1a5ca0f0b0ee3716d8d994af3bacc8f6b77cd130a944a839d23a2c7007cf"
|
|
||||||
],
|
|
||||||
"LICENSE": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd"
|
|
||||||
],
|
|
||||||
"Makefile": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"b7ab3a34e5faf79b96a8632039a0ad0aa87f2a9b5f0ba604e007cafb22190301"
|
|
||||||
],
|
|
||||||
"README.md": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"d2d26569f5a63b1be2e23835346ed2e8b0b13cdd74a6efb161221d2462a58dc5"
|
|
||||||
],
|
|
||||||
"bin/charm-env": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"fb6a20fac4102a6a4b6ffe903fcf666998f9a95a3647e6f9af7a1eeb44e58fd5"
|
|
||||||
],
|
|
||||||
"bin/layer_option": [
|
|
||||||
"layer:options",
|
|
||||||
"static",
|
|
||||||
"e959bf29da4c5edff28b2602c24113c4df9e25cdc9f2aa3b5d46c8577b2a40cc"
|
|
||||||
],
|
|
||||||
"build-calico-resource.sh": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"3573c7da514f7af01bae37d60ea048af50c23ba50f2c6ead7f301f3389712677"
|
|
||||||
],
|
|
||||||
"config.yaml": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"a144a241ca3c0a34a2ce0295c3a0946eb8860c3686388b4ac39683e5a69f8752"
|
|
||||||
],
|
|
||||||
"copyright": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"7c0e36e618a8544faaaa3f8e0533c2f1f4a18bcacbdd8b99b537742e6b587d58"
|
|
||||||
],
|
|
||||||
"copyright.layer-basic": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629"
|
|
||||||
],
|
|
||||||
"copyright.layer-leadership": [
|
|
||||||
"layer:leadership",
|
|
||||||
"static",
|
|
||||||
"8ce407829378fc0f72ce44c7f624e4951c7ccb3db1cfb949bee026b701728cc9"
|
|
||||||
],
|
|
||||||
"copyright.layer-options": [
|
|
||||||
"layer:options",
|
|
||||||
"static",
|
|
||||||
"f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629"
|
|
||||||
],
|
|
||||||
"docs/status.md": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"975dec9f8c938196e102e954a80226bda293407c4e5ae857c118bf692154702a"
|
|
||||||
],
|
|
||||||
"exec.d/docker-compose/charm-pre-install": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"2760db1047cdc4beeb974923c693bf824d45a9ee88fb50496efada92461aceb8"
|
|
||||||
],
|
|
||||||
"hooks/cni-relation-broken": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/cni-relation-changed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/cni-relation-created": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/cni-relation-departed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/cni-relation-joined": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/config-changed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/etcd-relation-broken": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/etcd-relation-changed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/etcd-relation-created": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/etcd-relation-departed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/etcd-relation-joined": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/hook.template": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/install": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/leader-elected": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/leader-settings-changed": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/post-series-upgrade": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/pre-series-upgrade": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/.gitignore": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"cf237c7aff44efbe6e502e645c3e06da03a69d7bdeb43392108ef3348143417e"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/README.md": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"93873d073f5f5302d352e09321aaf87458556e9730f89e1c682699c1d0db2386"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/__init__.py": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/interface.yaml": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"ba9f723b57a434f7efb2c06abec4167cd412c16da5f496a477dd7691e9a715be"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/peers.py": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"99419c3d139fb5bb90021e0482f9e7ac2cfb776fb7af79b46209c6a75b36e834"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/provides.py": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"3db1f644ab669e2dec59d59b61de63b721bc05b38fe646e525fff8f0d60982f9"
|
|
||||||
],
|
|
||||||
"hooks/relations/etcd/requires.py": [
|
|
||||||
"interface:etcd",
|
|
||||||
"static",
|
|
||||||
"8ffc1a094807fd36a1d1428b0a07b2428074134d46086066ecd6c0acd9fcd13e"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/.github/workflows/tests.yaml": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"d0015cd49675976ff87832f5ef7ea20ffca961786379c72bb6acdbdeddd9137c"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/.gitignore": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"0594213ebf9c6ef87827b30405ee67d847f73f4185a865e0e5e9c0be9d29eabe"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/README.md": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/__init__.py": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/interface.yaml": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"03affdaf7e879adfdf8c434aa31d40faa6d2872faa7dfd93a5d3a1ebae02487d"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/provides.py": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"2da15a0d547c3d3a6fb4745078a54d61136362c343fdf8635de14dbf714ba264"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/requires.py": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"2544a8ea5f5947f8b729a0db1efe9506d2bba819ba2798eba1437a6a725c17d4"
|
|
||||||
],
|
|
||||||
"hooks/relations/kubernetes-cni/tox.ini": [
|
|
||||||
"interface:kubernetes-cni",
|
|
||||||
"static",
|
|
||||||
"f08626c9b65362031edb07f96f15f101bc3dda075abc64f54d1c83efd2c05e39"
|
|
||||||
],
|
|
||||||
"hooks/start": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/stop": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/update-status": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"hooks/upgrade-charm": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
|
|
||||||
],
|
|
||||||
"icon.svg": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"49b68e61506d639d3c859e9477338469d1d44f7b76ad381ff152c728c71c43d9"
|
|
||||||
],
|
|
||||||
"layer.yaml": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"3a95aaa6fd50027d9a98ad9322568cfb0c228135df7cbff79953a86d01ec533f"
|
|
||||||
],
|
|
||||||
"lib/calico_common.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"ec886f86a4505148016a540652c51afd7bf8ee4ef3b21db368e10ded2b9569be"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/__init__.py": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"dfe0d26c6bf409767de6e2546bc648f150e1b396243619bad3aa0553ab7e0e6f"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/basic.py": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"d120158e0c305a3b4529426a1a63a2f59af4f5730dccf3a59a9ffe1988494cee"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/execd.py": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"fda8bd491032db1db8ddaf4e99e7cc878c6fb5432efe1f91cadb5b34765d076d"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/kubernetes_common.py": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"bc89bd609a8e94102e00a192b7ae3caa813cca5e356536330494742bfdb6c4cb"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/options.py": [
|
|
||||||
"layer:options",
|
|
||||||
"static",
|
|
||||||
"8ae7a07d22542fc964f2d2bee8219d1c78a68dace70a1b38d36d4aea47b1c3b2"
|
|
||||||
],
|
|
||||||
"lib/charms/layer/status.py": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"d560a5e07b2e5f2b0f25f30e1f0278b06f3f90c01e4dbad5c83d71efc79018c6"
|
|
||||||
],
|
|
||||||
"lib/charms/leadership.py": [
|
|
||||||
"layer:leadership",
|
|
||||||
"static",
|
|
||||||
"20ffcbbc08147506759726ad51567420659ffb8a2e0121079240b8706658e332"
|
|
||||||
],
|
|
||||||
"make_docs": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"c990f55c8e879793a62ed8464ee3d7e0d7d2225fdecaf17af24b0df0e2daa8c1"
|
|
||||||
],
|
|
||||||
"manifest.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"3e2188673e3823cc86b01210285fac1a1960e6802574b75a4094d53cfd521191"
|
|
||||||
],
|
|
||||||
"metadata.yaml": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"57ae67fde84b2cdd113b26d98a471750414118173d82c8c7ce2567c8155f65b9"
|
|
||||||
],
|
|
||||||
"pydocmd.yml": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"11d9293901f32f75f4256ae4ac2073b92ce1d7ef7b6c892ba9fbb98690a0b330"
|
|
||||||
],
|
|
||||||
"reactive/__init__.py": [
|
|
||||||
"layer:leadership",
|
|
||||||
"static",
|
|
||||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
||||||
],
|
|
||||||
"reactive/calico.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"9f9af6a5c107c30406ec122bfed41c03184eff4165da0d00e8069c2f3b7a4f7d"
|
|
||||||
],
|
|
||||||
"reactive/leadership.py": [
|
|
||||||
"layer:leadership",
|
|
||||||
"static",
|
|
||||||
"e2b233cf861adc3b2d9e9c062134ce2f104953f03283cdddd88f49efee652e8f"
|
|
||||||
],
|
|
||||||
"reactive/status.py": [
|
|
||||||
"layer:status",
|
|
||||||
"static",
|
|
||||||
"30207fc206f24e91def5252f1c7f7c8e23c0aed0e93076babf5e03c05296d207"
|
|
||||||
],
|
|
||||||
"requirements.txt": [
|
|
||||||
"layer:basic",
|
|
||||||
"static",
|
|
||||||
"a00f75d80849e5b4fc5ad2e7536f947c25b1a4044b341caa8ee87a92d3a4c804"
|
|
||||||
],
|
|
||||||
"templates/10-calico.conflist": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"9332e14d9422781530cd13fef5748e3d06fcce7f4221123f625c3a7e09238abb"
|
|
||||||
],
|
|
||||||
"templates/calico-node.service": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"5d1a8ab565c013bfa2e8dbd88b41d9944dc1fa4edc2faf8300fad4f89d4d0665"
|
|
||||||
],
|
|
||||||
"templates/calicoctl": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"b913dfdce8de51aa9a13950817e4101f8f4229052927a272fff5b37a4370537f"
|
|
||||||
],
|
|
||||||
"templates/cdk.auth-webhook-secret.yaml": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"efaf34c12a5c961fa7843199070945ba05717b3656a0f3acc3327f45334bcaec"
|
|
||||||
],
|
|
||||||
"templates/policy-controller.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"0c3e535fc246d98ff90e02e2bb99f213375dc5be2645150b1861f8178c3c1557"
|
|
||||||
],
|
|
||||||
"tests/data/bird-operator/charmcraft.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"7b34978de6b054b723c48aa9925b7c0acf672b162724a2ed67c841f56784b587"
|
|
||||||
],
|
|
||||||
"tests/data/bird-operator/config.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"4786605f043192ab2970b7abd55c434620463248e2840a7d25a9ca31d913b416"
|
|
||||||
],
|
|
||||||
"tests/data/bird-operator/metadata.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"aff75a91343249cb86978512609d0e00c9d6271664b18eeed9e4ef415bd22937"
|
|
||||||
],
|
|
||||||
"tests/data/bird-operator/requirements.txt": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"7a70b4e7059a7d283c883288be3de0bed02d10fda4602c8de4699debcf6afbf2"
|
|
||||||
],
|
|
||||||
"tests/data/bird-operator/src/charm.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"8e0374bf6e887604e3ede4ba33d37cf0e43202b653cb3945cefff0d2a33e7a0c"
|
|
||||||
],
|
|
||||||
"tests/data/bundle.yaml": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"0df5e82072c89d020d85120d44ad49c3f027fdd2234225539e3c1b600137e216"
|
|
||||||
],
|
|
||||||
"tests/data/ip_addr_json": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"f129576a9e2c7738aca8669c642f123534eda63121ae450cec4cbda787b1eb06"
|
|
||||||
],
|
|
||||||
"tests/functional/conftest.py": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"fd53e0c38b4dda0c18096167889cd0d85b98b0a13225f9f8853261241e94078c"
|
|
||||||
],
|
|
||||||
"tests/functional/test_k8s_common.py": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"680a53724154771dd78422bbaf24b151788d86dd07960712c5d9e0d758499b50"
|
|
||||||
],
|
|
||||||
"tests/integration/conftest.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"6ba0b2263cc6acaae7ea9df261b895c6bc13d4bf4809a7a25fbed854b88079e6"
|
|
||||||
],
|
|
||||||
"tests/integration/test_calico_integration.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"2559b5d5a8b1d9318f8f0f8a7bb619425277821f072ce804ad7f593eaed9e2e8"
|
|
||||||
],
|
|
||||||
"tests/unit/conftest.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"2c58cb11bf276805f586c05c20bf4ba15a7431b5c23ea3323dc4256ddc34c4d2"
|
|
||||||
],
|
|
||||||
"tests/unit/test_calico.py": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"cf25effe2454f90c2d7ee7b9c5a3003571c45d067be798bf5e7a37cff9a30e30"
|
|
||||||
],
|
|
||||||
"tests/unit/test_k8s_common.py": [
|
|
||||||
"layer:kubernetes-common",
|
|
||||||
"static",
|
|
||||||
"23e097e7f21e4f4f062caac0146bb85373e895a30be1be5667b90d0e84435882"
|
|
||||||
],
|
|
||||||
"tests/validate-wheelhouse.sh": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"cdfd66832b110243b6fd165a75562d9b958f9741b334be2d3a7a1d05adfa6fe7"
|
|
||||||
],
|
|
||||||
"tox.ini": [
|
|
||||||
"calico",
|
|
||||||
"static",
|
|
||||||
"148960f480c0754069a7e4b190993de77c44f2ccb8203177a1a538bb5117e470"
|
|
||||||
],
|
|
||||||
"version": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"a36d32d4b537bff7998870faf8069acd3e73541bab3bc95f15ba95ad12ec9e99"
|
|
||||||
],
|
|
||||||
"wheelhouse.txt": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"1b3985edd8c124da3cf37b217e66029c9bd0865d93dd786e96c70838dae3fcff"
|
|
||||||
],
|
|
||||||
"wheelhouse/Jinja2-3.0.3.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"
|
|
||||||
],
|
|
||||||
"wheelhouse/MarkupSafe-2.0.1.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"
|
|
||||||
],
|
|
||||||
"wheelhouse/PyYAML-5.3.1.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"
|
|
||||||
],
|
|
||||||
"wheelhouse/charmhelpers-1.2.1.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"298bb9e90d9392e2b66d10a5199b1b2d459dc8d5434b897913325904989dd2d7"
|
|
||||||
],
|
|
||||||
"wheelhouse/charms.reactive-1.5.0.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"b56484ed17f412c7738ff21e4ddc0e7c758af2288eac9fe521a86c8c31c1b150"
|
|
||||||
],
|
|
||||||
"wheelhouse/click-7.1.2.tar.gz": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"
|
|
||||||
],
|
|
||||||
"wheelhouse/conctl-0.1.3.zip": [
|
|
||||||
"calico",
|
|
||||||
"dynamic",
|
|
||||||
"bdf965b70af0ff578306bea2c02e1a31542c0732a5791bf768588f8dd58ddd3d"
|
|
||||||
],
|
|
||||||
"wheelhouse/netaddr-0.7.19.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd"
|
|
||||||
],
|
|
||||||
"wheelhouse/pbr-5.9.0.tar.gz": [
|
|
||||||
"__pip__",
|
|
||||||
"dynamic",
|
|
||||||
"e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"
|
|
||||||
],
|
|
||||||
"wheelhouse/pip-18.1.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"c0a292bd977ef590379a3f05d7b7f65135487b67470f6281289a94e015650ea1"
|
|
||||||
],
|
|
||||||
"wheelhouse/pyaml-21.10.1.tar.gz": [
|
|
||||||
"__pip__",
|
|
||||||
"dynamic",
|
|
||||||
"c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"
|
|
||||||
],
|
|
||||||
"wheelhouse/setuptools-41.6.0.zip": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"6afa61b391dcd16cb8890ec9f66cc4015a8a31a6e1c2b4e0c464514be1a3d722"
|
|
||||||
],
|
|
||||||
"wheelhouse/setuptools_scm-1.17.0.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"70a4cf5584e966ae92f54a764e6437af992ba42ac4bca7eb37cc5d02b98ec40a"
|
|
||||||
],
|
|
||||||
"wheelhouse/wheel-0.33.6.tar.gz": [
|
|
||||||
"layer:basic",
|
|
||||||
"dynamic",
|
|
||||||
"10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
name: Test Suite
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-unit:
|
|
||||||
name: Lint Unit
|
|
||||||
uses: charmed-kubernetes/workflows/.github/workflows/lint-unit.yaml@main
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
name: Run tests with Tox
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
call-inclusive-naming-check:
|
|
||||||
name: Inclusive Naming
|
|
||||||
uses: canonical-web-and-design/Inclusive-naming/.github/workflows/woke.yaml@main
|
|
||||||
with:
|
|
||||||
fail-on-error: "true"
|
|
||||||
|
|
||||||
lint-unit:
|
|
||||||
name: Lint Unit
|
|
||||||
uses: charmed-kubernetes/workflows/.github/workflows/lint-unit.yaml@main
|
|
||||||
|
|
||||||
validate-wheelhouse:
|
|
||||||
uses: charmed-kubernetes/workflows/.github/workflows/validate-wheelhouse.yaml@main
|
|
||||||
|
|
||||||
integration-test:
|
|
||||||
name: Integration test with VMWare
|
|
||||||
runs-on: self-hosted
|
|
||||||
timeout-minutes: 360
|
|
||||||
needs:
|
|
||||||
- call-inclusive-naming-check
|
|
||||||
- lint-unit
|
|
||||||
- validate-wheelhouse
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y python3-venv
|
|
||||||
- name: Setup operator environment
|
|
||||||
uses: charmed-kubernetes/actions-operator@main
|
|
||||||
with:
|
|
||||||
provider: vsphere
|
|
||||||
credentials-yaml: ${{ secrets.CREDENTIALS_YAML }}
|
|
||||||
clouds-yaml: ${{ secrets.CLOUDS_YAML }}
|
|
||||||
bootstrap-options: "--model-default datastore=vsanDatastore --model-default primary-network=VLAN_2764"
|
|
||||||
- name: Run test
|
|
||||||
run: tox -e integration -- --destructive-mode
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.tox/
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
# Contributor Guide
|
|
||||||
|
|
||||||
This Juju charm is open source ([Apache License 2.0](./LICENSE)) and we actively seek any community contibutions
|
|
||||||
for code, suggestions and documentation.
|
|
||||||
This page details a few notes, workflows and suggestions for how to make contributions most effective and help us
|
|
||||||
all build a better charm - please give them a read before working on any contributions.
|
|
||||||
|
|
||||||
## Licensing
|
|
||||||
|
|
||||||
This charm has been created under the [Apache License 2.0](./LICENSE), which will cover any contributions you may
|
|
||||||
make to this project. Please familiarise yourself with the terms of the license.
|
|
||||||
|
|
||||||
Additionally, this charm uses the Harmony CLA agreement. It’s the easiest way for you to give us permission to
|
|
||||||
use your contributions.
|
|
||||||
In effect, you’re giving us a license, but you still own the copyright — so you retain the right to modify your
|
|
||||||
code and use it in other projects. Please [sign the CLA here](https://ubuntu.com/legal/contributors/agreement) before
|
|
||||||
making any contributions.
|
|
||||||
|
|
||||||
## Code of conduct
|
|
||||||
We have adopted the Ubuntu code of Conduct. You can read this in full [here](https://ubuntu.com/community/code-of-conduct).
|
|
||||||
|
|
||||||
## Contributing code
|
|
||||||
|
|
||||||
The [DEVELOPING.md](./DEVELOPING.md) page has some useful information regarding building and testing. To contribute code
|
|
||||||
to this project, the workflow is as follows:
|
|
||||||
|
|
||||||
1. [Submit a bug](https://bugs.launchpad.net/charm-calico/+filebug) to explain the need for and track the change.
|
|
||||||
2. Create a branch on your fork of the repo with your changes, including a unit test covering the new or modified code.
|
|
||||||
3. Submit a PR. The PR description should include a link to the bug on Launchpad.
|
|
||||||
4. Update the Launchpad bug to include a link to the PR and the `review-needed` tag.
|
|
||||||
5. Once reviewed and merged, the change will become available on the edge channel and assigned to an appropriate milestone
|
|
||||||
for further release according to priority.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Documentation for this charm is currently maintained as part of the Charmed Kubernetes docs.
|
|
||||||
See [this page](https://github.com/charmed-kubernetes/kubernetes-docs/blob/main/pages/k8s/charm-calico.md)
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
# Developing layer-calico
|
|
||||||
|
|
||||||
## Installing build dependencies
|
|
||||||
|
|
||||||
To install build dependencies:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo snap install charm --classic
|
|
||||||
sudo apt install docker.io
|
|
||||||
sudo usermod -aG docker $USER
|
|
||||||
```
|
|
||||||
|
|
||||||
After running these commands, terminate your shell session and start a new one
|
|
||||||
to pick up the modified user groups.
|
|
||||||
|
|
||||||
## Building the charm
|
|
||||||
|
|
||||||
To build the charm:
|
|
||||||
```
|
|
||||||
charm build
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, this will build the charm and place it in
|
|
||||||
`/tmp/charm-builds/calico`.
|
|
||||||
|
|
||||||
## Building resources
|
|
||||||
|
|
||||||
To build resources:
|
|
||||||
```
|
|
||||||
./build-calico-resources.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will produce several .tar.gz files that you will need to attach to the
|
|
||||||
charm when you deploy it.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
You can test a locally built calico charm by deploying it with Charmed
|
|
||||||
Kubernetes.
|
|
||||||
|
|
||||||
Create a file named `local-calico.yaml` that contains the following (with paths
|
|
||||||
adjusted to fit your environment):
|
|
||||||
```
|
|
||||||
applications:
|
|
||||||
calico:
|
|
||||||
charm: /tmp/charm-builds/calico
|
|
||||||
resources:
|
|
||||||
calico: /path/to/layer-calico/calico-amd64.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
Then deploy Charmed Kubernetes with your locally built calico charm:
|
|
||||||
|
|
||||||
```
|
|
||||||
juju deploy cs:~containers/kubernetes-calico --overlay local-calico.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helpful links
|
|
||||||
|
|
||||||
* [Getting Started with charm development](https://jaas.ai/docs/getting-started-with-charm-development)
|
|
||||||
* [Charm tools documentation](https://jaas.ai/docs/charm-tools)
|
|
||||||
* [Charmed Kubernetes Calico documentation](https://ubuntu.com/kubernetes/docs/cni-calico)
|
|
||||||
202
calico/LICENSE
202
calico/LICENSE
|
|
@ -1,202 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#!/usr/bin/make
|
|
||||||
|
|
||||||
all: lint unit_test
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
@rm -rf .tox
|
|
||||||
|
|
||||||
.PHONY: apt_prereqs
|
|
||||||
apt_prereqs:
|
|
||||||
@# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
|
|
||||||
@which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: apt_prereqs
|
|
||||||
@tox --notest
|
|
||||||
@PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
|
|
||||||
@charm proof
|
|
||||||
|
|
||||||
.PHONY: unit_test
|
|
||||||
unit_test: apt_prereqs
|
|
||||||
@echo Starting tests...
|
|
||||||
tox
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
# Calico Charm
|
|
||||||
|
|
||||||
Calico is a new approach to virtual networking and network security for containers,
|
|
||||||
VMs, and bare metal services, that provides a rich set of security enforcement
|
|
||||||
capabilities running on top of a highly scalable and efficient virtual network fabric.
|
|
||||||
|
|
||||||
This charm will deploy calico as a background service, and configure CNI for
|
|
||||||
use with calico, on any principal charm that implements the [kubernetes-cni][]
|
|
||||||
interface.
|
|
||||||
|
|
||||||
This charm is a component of Charmed Kubernetes. For full information,
|
|
||||||
please visit the [official Charmed Kubernetes docs](https://www.ubuntu.com/kubernetes/docs/charm-calico).
|
|
||||||
|
|
||||||
[kubernetes-cni]: https://github.com/juju-solutions/interface-kubernetes-cni
|
|
||||||
|
|
||||||
# Developers
|
|
||||||
|
|
||||||
## Build charm
|
|
||||||
|
|
||||||
```
|
|
||||||
make charm
|
|
||||||
```
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
VERSION="1.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
find_charm_dirs() {
|
|
||||||
# Hopefully, $JUJU_CHARM_DIR is set so which venv to use in unambiguous.
|
|
||||||
if [[ -n "$JUJU_CHARM_DIR" || -n "$CHARM_DIR" ]]; then
|
|
||||||
if [[ -z "$JUJU_CHARM_DIR" ]]; then
|
|
||||||
# accept $CHARM_DIR to be more forgiving
|
|
||||||
export JUJU_CHARM_DIR="$CHARM_DIR"
|
|
||||||
fi
|
|
||||||
if [[ -z "$CHARM_DIR" ]]; then
|
|
||||||
# set CHARM_DIR as well to help with backwards compatibility
|
|
||||||
export CHARM_DIR="$JUJU_CHARM_DIR"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
# Try to guess the value for JUJU_CHARM_DIR by looking for a non-subordinate
|
|
||||||
# (because there's got to be at least one principle) charm directory;
|
|
||||||
# if there are several, pick the first by alpha order.
|
|
||||||
agents_dir="/var/lib/juju/agents"
|
|
||||||
if [[ -d "$agents_dir" ]]; then
|
|
||||||
desired_charm="$1"
|
|
||||||
found_charm_dir=""
|
|
||||||
if [[ -n "$desired_charm" ]]; then
|
|
||||||
for charm_dir in $(/bin/ls -d "$agents_dir"/unit-*/charm); do
|
|
||||||
charm_name="$(grep -o '^['\''"]\?name['\''"]\?:.*' $charm_dir/metadata.yaml 2> /dev/null | sed -e 's/.*: *//' -e 's/['\''"]//g')"
|
|
||||||
if [[ "$charm_name" == "$desired_charm" ]]; then
|
|
||||||
if [[ -n "$found_charm_dir" ]]; then
|
|
||||||
>&2 echo "Ambiguous possibilities for JUJU_CHARM_DIR matching '$desired_charm'; please run within a Juju hook context"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
found_charm_dir="$charm_dir"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ -z "$found_charm_dir" ]]; then
|
|
||||||
>&2 echo "Unable to determine JUJU_CHARM_DIR matching '$desired_charm'; please run within a Juju hook context"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
export JUJU_CHARM_DIR="$found_charm_dir"
|
|
||||||
export CHARM_DIR="$found_charm_dir"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
# shellcheck disable=SC2126
|
|
||||||
non_subordinates="$(grep -L 'subordinate"\?:.*true' "$agents_dir"/unit-*/charm/metadata.yaml | wc -l)"
|
|
||||||
if [[ "$non_subordinates" -gt 1 ]]; then
|
|
||||||
>&2 echo 'Ambiguous possibilities for JUJU_CHARM_DIR; please use --charm or run within a Juju hook context'
|
|
||||||
exit 1
|
|
||||||
elif [[ "$non_subordinates" -eq 1 ]]; then
|
|
||||||
for charm_dir in $(/bin/ls -d "$agents_dir"/unit-*/charm); do
|
|
||||||
if grep -q 'subordinate"\?:.*true' "$charm_dir/metadata.yaml"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
export JUJU_CHARM_DIR="$charm_dir"
|
|
||||||
export CHARM_DIR="$charm_dir"
|
|
||||||
return
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
>&2 echo 'Unable to determine JUJU_CHARM_DIR; please run within a Juju hook context'
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
try_activate_venv() {
|
|
||||||
if [[ -d "$JUJU_CHARM_DIR/../.venv" ]]; then
|
|
||||||
. "$JUJU_CHARM_DIR/../.venv/bin/activate"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
find_wrapped() {
|
|
||||||
PATH="${PATH/\/usr\/local\/sbin:}" which "$(basename "$0")"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if [[ "$1" == "--version" || "$1" == "-v" ]]; then
|
|
||||||
echo "$VERSION"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# allow --charm option to hint which JUJU_CHARM_DIR to choose when ambiguous
|
|
||||||
# NB: --charm option must come first
|
|
||||||
# NB: option must be processed outside find_charm_dirs to modify $@
|
|
||||||
charm_name=""
|
|
||||||
if [[ "$1" == "--charm" ]]; then
|
|
||||||
charm_name="$2"
|
|
||||||
shift; shift
|
|
||||||
fi
|
|
||||||
|
|
||||||
find_charm_dirs "$charm_name"
|
|
||||||
try_activate_venv
|
|
||||||
export PYTHONPATH="$JUJU_CHARM_DIR/lib:$PYTHONPATH"
|
|
||||||
|
|
||||||
if [[ "$(basename "$0")" == "charm-env" ]]; then
|
|
||||||
# being used as a shebang
|
|
||||||
exec "$@"
|
|
||||||
elif [[ "$0" == "$BASH_SOURCE" ]]; then
|
|
||||||
# being invoked as a symlink wrapping something to find in the venv
|
|
||||||
exec "$(find_wrapped)" "$@"
|
|
||||||
elif [[ "$(basename "$BASH_SOURCE")" == "charm-env" ]]; then
|
|
||||||
# being sourced directly; do nothing
|
|
||||||
/bin/true
|
|
||||||
else
|
|
||||||
# being sourced for wrapped bash helpers
|
|
||||||
. "$(find_wrapped)"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import argparse
|
|
||||||
from charms import layer
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Access layer options.')
|
|
||||||
parser.add_argument('section',
|
|
||||||
help='the section, or layer, the option is from')
|
|
||||||
parser.add_argument('option',
|
|
||||||
help='the option to access')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
value = layer.options.get(args.section, args.option)
|
|
||||||
if isinstance(value, bool):
|
|
||||||
sys.exit(0 if value else 1)
|
|
||||||
elif isinstance(value, list):
|
|
||||||
for val in value:
|
|
||||||
print(val)
|
|
||||||
else:
|
|
||||||
print(value)
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
# This script will fetch binaries and create resource tarballs for use by
|
|
||||||
# charm-[push|release]. The arm64 binaries are not available upsteram for
|
|
||||||
# v2.6, so we must build them and host them somewhere ourselves. The steps
|
|
||||||
# for doing that are documented here:
|
|
||||||
#
|
|
||||||
# https://gist.github.com/kwmonroe/9b5f8dac2c17f93629a1a3868b22d671
|
|
||||||
|
|
||||||
# Supported calico architectures
|
|
||||||
arches="amd64 arm64"
|
|
||||||
calico_version="v3.21.4"
|
|
||||||
|
|
||||||
function fetch_and_validate() {
|
|
||||||
# fetch a binary and make sure it's what we expect (executable > 20MB)
|
|
||||||
min_bytes=20000000
|
|
||||||
location="${1-}"
|
|
||||||
if [ -z ${location} ]; then
|
|
||||||
echo "$0: Missing location parameter for fetch_and_validate"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# remove everything up until the last slash to get the filename
|
|
||||||
filename=$(echo "${location##*/}")
|
|
||||||
case ${location} in
|
|
||||||
http*)
|
|
||||||
fetch_cmd="wget ${location} -O ./${filename}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
fetch_cmd="scp ${location} ./${filename}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
${fetch_cmd}
|
|
||||||
|
|
||||||
# Make sure we fetched something big enough
|
|
||||||
actual_bytes=$(wc -c < ${filename})
|
|
||||||
if [ $actual_bytes -le $min_bytes ]; then
|
|
||||||
echo "$0: ${filename} should be at least ${min_bytes} bytes"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make sure we fetched a binary
|
|
||||||
if ! file ${filename} 2>&1 | grep -q 'executable'; then
|
|
||||||
echo "$0: ${filename} is not an executable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
wget \
|
|
||||||
https://github.com/projectcalico/calico/releases/download/$calico_version/release-$calico_version.tgz
|
|
||||||
tar -xf release-$calico_version.tgz
|
|
||||||
|
|
||||||
for arch in ${arches}; do
|
|
||||||
rm -rf resource-build-$arch
|
|
||||||
mkdir resource-build-$arch
|
|
||||||
pushd resource-build-$arch
|
|
||||||
cp ../release-$calico_version/bin/calicoctl/calicoctl-linux-$arch calicoctl
|
|
||||||
cp ../release-$calico_version/bin/cni/$arch/calico calico
|
|
||||||
cp ../release-$calico_version/bin/cni/$arch/calico-ipam calico-ipam
|
|
||||||
|
|
||||||
tar -zcvf ../calico-$arch.tar.gz .
|
|
||||||
|
|
||||||
popd
|
|
||||||
rm -rf resource-build-$arch
|
|
||||||
done
|
|
||||||
|
|
||||||
rm -rf release-$calico_version.tgz release-$calico_version
|
|
||||||
|
|
||||||
touch calico-node-image.tar.gz
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
"options":
|
|
||||||
"bgp-service-cluster-ips":
|
|
||||||
"type": "string"
|
|
||||||
"description": |
|
|
||||||
Space-separated list of service cluster CIDRs to advertise over BGP.
|
|
||||||
These will be passed to the .spec.serviceClusterIPs field of the default
|
|
||||||
BGPConfiguration in Calico.
|
|
||||||
|
|
||||||
Example value: ”10.0.0.0/24 10.0.1.0/24”
|
|
||||||
"default": ""
|
|
||||||
"bgp-service-external-ips":
|
|
||||||
"type": "string"
|
|
||||||
"description": |
|
|
||||||
Space-separated list of service external CIDRs to advertise over BGP.
|
|
||||||
These will be passed to the .spec.serviceExternalIPs field of the default
|
|
||||||
BGPConfiguration in Calico.
|
|
||||||
|
|
||||||
Example value: ”10.0.0.0/24 10.0.1.0/24”
|
|
||||||
"default": ""
|
|
||||||
"bgp-service-loadbalancer-ips":
|
|
||||||
"type": "string"
|
|
||||||
"description": |
|
|
||||||
Space-separated list of service load-balancer CIDRs to advertise over BGP.
|
|
||||||
These will be passed to the .spec.serviceLoadBalancerIPs field of the default
|
|
||||||
BGPConfiguration in Calico.
|
|
||||||
|
|
||||||
Example value: ”10.0.0.0/24 10.0.1.0/24”
|
|
||||||
"default": ""
|
|
||||||
"calico-node-image":
|
|
||||||
"type": "string"
|
|
||||||
# Please refer to layer-canal/versioning.md before changing the version below.
|
|
||||||
"default": "rocks.canonical.com:443/cdk/calico/node:v3.21.4"
|
|
||||||
"description": |
|
|
||||||
The image id to use for calico/node.
|
|
||||||
"calico-policy-image":
|
|
||||||
"type": "string"
|
|
||||||
"default": "rocks.canonical.com:443/cdk/calico/kube-controllers:v3.21.4"
|
|
||||||
"description": |
|
|
||||||
The image id to use for calico/kube-controllers.
|
|
||||||
"ipip":
|
|
||||||
"type": "string"
|
|
||||||
"default": "Never"
|
|
||||||
"description": |
|
|
||||||
IPIP encapsulation mode. Must be one of "Always", "CrossSubnet", or "Never".
|
|
||||||
This is incompatible with VXLAN encapsulation. If VXLAN encapsulation is
|
|
||||||
enabled, then this must be set to "Never".
|
|
||||||
"vxlan":
|
|
||||||
"type": "string"
|
|
||||||
"default": "Never"
|
|
||||||
"description": |
|
|
||||||
VXLAN encapsulation mode. Must be one of "Always", "CrossSubnet", or "Never".
|
|
||||||
This is incompatible with IPIP encapsulation. If IPIP encapsulation is
|
|
||||||
enabled, then this must be set to "Never".
|
|
||||||
"veth-mtu":
|
|
||||||
"type": "int"
|
|
||||||
"default": !!null ""
|
|
||||||
"description": |
|
|
||||||
Set veth MTU size. This should be set to the MTU size of the base network.
|
|
||||||
|
|
||||||
If VXLAN is enabled, then the charm will automatically subtract 50 from the
|
|
||||||
specified MTU size.
|
|
||||||
|
|
||||||
If IPIP is enabled, then the charm will automatically subtract 20 from the
|
|
||||||
specified MTU size.
|
|
||||||
"nat-outgoing":
|
|
||||||
"type": "boolean"
|
|
||||||
"default": !!bool "true"
|
|
||||||
"description": |
|
|
||||||
NAT outgoing traffic
|
|
||||||
"cidr":
|
|
||||||
"type": "string"
|
|
||||||
"default": "192.168.0.0/16"
|
|
||||||
"description": |
|
|
||||||
Network CIDR assigned to Calico. This is applied to the default Calico
|
|
||||||
pool, and is also communicated to the Kubernetes charms for use in
|
|
||||||
kube-proxy configuration.
|
|
||||||
|
|
||||||
Calico assigns IP addresses to Kubernetes nodes in blocks of 64 addresses.
|
|
||||||
It is recommended to make the Calico network large enough to assign at
|
|
||||||
least one 64 address block to each kubernetes-control-plane and
|
|
||||||
kubernetes-worker unit.
|
|
||||||
"manage-pools":
|
|
||||||
"type": "boolean"
|
|
||||||
"default": !!bool "true"
|
|
||||||
"description": |
|
|
||||||
If true, a default pool is created using the cidr and ipip charm
|
|
||||||
configuration values.
|
|
||||||
|
|
||||||
Warning: When manage-pools is enabled, the charm will delete any pools
|
|
||||||
that are unrecognized.
|
|
||||||
"global-as-number":
|
|
||||||
"type": "int"
|
|
||||||
"default": !!int "64512"
|
|
||||||
"description": |
|
|
||||||
Global AS number.
|
|
||||||
"subnet-as-numbers":
|
|
||||||
"type": "string"
|
|
||||||
"default": "{}"
|
|
||||||
"description": |
|
|
||||||
Mapping of subnets to AS numbers, specified as YAML. Each Calico node
|
|
||||||
will be assigned an AS number based on the entries in this mapping.
|
|
||||||
|
|
||||||
Example value: "{10.0.0.0/24: 64512, 10.0.1.0/24: 64513}"
|
|
||||||
|
|
||||||
If a node's IP matches any of the specified subnets, then the
|
|
||||||
corresponding AS number is used instead of the global one.
|
|
||||||
|
|
||||||
If a node's IP matches no subnets, then the global AS number will be
|
|
||||||
used instead.
|
|
||||||
|
|
||||||
If a node's IP matches multiple subnets, then the most specific subnet
|
|
||||||
will be used, e.g. a /24 subnet will take precedence over a /16.
|
|
||||||
"unit-as-numbers":
|
|
||||||
"type": "string"
|
|
||||||
"default": "{}"
|
|
||||||
"description": |
|
|
||||||
Mapping of unit IDs to AS numbers, specified as YAML. Each Calico node
|
|
||||||
will be assigned an AS number based on the entries in this mapping.
|
|
||||||
|
|
||||||
Example value: "{0: 64512, 1: 64513}"
|
|
||||||
|
|
||||||
This takes precedence over global-as-number and subnet-as-numbers.
|
|
||||||
"node-to-node-mesh":
|
|
||||||
"type": "boolean"
|
|
||||||
"default": !!bool "true"
|
|
||||||
"description": |
|
|
||||||
When enabled, each Calico node will peer with every other Calico node in
|
|
||||||
the cluster.
|
|
||||||
"global-bgp-peers":
|
|
||||||
"type": "string"
|
|
||||||
"default": "[]"
|
|
||||||
"description": |
|
|
||||||
List of global BGP peers. Each BGP peer is specified with an address and
|
|
||||||
an as-number.
|
|
||||||
|
|
||||||
Example value: "[{address: 10.0.0.1, as-number: 65000}, {address: 10.0.0.2, as-number: 65001}]"
|
|
||||||
"subnet-bgp-peers":
|
|
||||||
"type": "string"
|
|
||||||
"default": "{}"
|
|
||||||
"description": |
|
|
||||||
Mapping of subnets to lists of BGP peers. Each BGP peer is specified with
|
|
||||||
an address and an as-number.
|
|
||||||
|
|
||||||
Example value: "{10.0.0.0/24: [{address: 10.0.0.1, as-number: 65000}, {address: 10.0.0.2, as-number: 65001}], 10.0.1.0/24: [{address: 10.0.1.1, as-number: 65002}]}"
|
|
||||||
|
|
||||||
If a node's IP matches multiple subnets, then peerings will be added for
|
|
||||||
each matched subnet.
|
|
||||||
"unit-bgp-peers":
|
|
||||||
"type": "string"
|
|
||||||
"default": "{}"
|
|
||||||
"description": |
|
|
||||||
Mapping of unit IDs to lists of BGP peers. Each BGP peer is specified
|
|
||||||
with an address and an as-number.
|
|
||||||
|
|
||||||
Example value: "{0: [{address: 10.0.0.1, as-number: 65000}, {address: 10.0.0.2, as-number: 65001}], 1: [{address: 10.0.1.1, as-number: 65002}]}"
|
|
||||||
"route-reflector-cluster-ids":
|
|
||||||
"type": "string"
|
|
||||||
"default": "{}"
|
|
||||||
"description": |
|
|
||||||
Mapping of unit IDs to route reflector cluster IDs. Assigning a route
|
|
||||||
reflector cluster ID allows the node to function as a route reflector.
|
|
||||||
|
|
||||||
Example value: "{0: 224.0.0.1, 2: 224.0.0.1}"
|
|
||||||
"ignore-loose-rpf":
|
|
||||||
"type": "boolean"
|
|
||||||
"default": !!bool "false"
|
|
||||||
"description": |
|
|
||||||
Enable or disable IgnoreLooseRPF for Calico Felix. This is only used
|
|
||||||
when rp_filter is set to a value of 2.
|
|
||||||
"disable-vxlan-tx-checksumming":
|
|
||||||
"type": "boolean"
|
|
||||||
"default": !!bool "true"
|
|
||||||
"description": |
|
|
||||||
When set to true, if VXLAN encapsulation is in use, then the charm will
|
|
||||||
disable TX checksumming on the vxlan.calico network interface. This works
|
|
||||||
around an upstream issue in Calico:
|
|
||||||
https://github.com/projectcalico/calico/issues/3145
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
Format: http://dep.debian.net/deps/dep5/
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: Copyright 2018, Canonical Ltd., All Rights Reserved.
|
|
||||||
License: Apache License 2.0
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
.
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
.
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
Format: http://dep.debian.net/deps/dep5/
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: Copyright 2015-2017, Canonical Ltd., All Rights Reserved.
|
|
||||||
License: Apache License 2.0
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
.
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
.
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
Copyright 2015-2016 Canonical Ltd.
|
|
||||||
|
|
||||||
This file is part of the Leadership Layer for Juju.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License version 3, as
|
|
||||||
published by the Free Software Foundation.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but
|
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranties of
|
|
||||||
MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
Format: http://dep.debian.net/deps/dep5/
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: Copyright 2015-2017, Canonical Ltd., All Rights Reserved.
|
|
||||||
License: Apache License 2.0
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
.
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
.
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
<h1 id="charms.layer.status.WorkloadState">WorkloadState</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
WorkloadState(self, /, *args, **kwargs)
|
|
||||||
```
|
|
||||||
|
|
||||||
Enum of the valid workload states.
|
|
||||||
|
|
||||||
Valid options are:
|
|
||||||
|
|
||||||
* `WorkloadState.MAINTENANCE`
|
|
||||||
* `WorkloadState.BLOCKED`
|
|
||||||
* `WorkloadState.WAITING`
|
|
||||||
* `WorkloadState.ACTIVE`
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.maintenance">maintenance</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
maintenance(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the status to the `MAINTENANCE` state with the given operator message.
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.maint">maint</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
maint(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Shorthand alias for
|
|
||||||
[maintenance](status.md#charms.layer.status.maintenance).
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.blocked">blocked</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
blocked(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the status to the `BLOCKED` state with the given operator message.
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.waiting">waiting</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
waiting(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the status to the `WAITING` state with the given operator message.
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.active">active</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
active(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the status to the `ACTIVE` state with the given operator message.
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
<h1 id="charms.layer.status.status_set">status_set</h1>
|
|
||||||
|
|
||||||
```python
|
|
||||||
status_set(workload_state, message)
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the status to the given workload state with a message.
|
|
||||||
|
|
||||||
__Parameters__
|
|
||||||
|
|
||||||
- __`workload_state` (WorkloadState or str)__: State of the workload. Should be
|
|
||||||
a [WorkloadState](status.md#charms.layer.status.WorkloadState) enum
|
|
||||||
member, or the string value of one of those members.
|
|
||||||
- __`message` (str)__: Message to convey to the operator.
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# This stubs out charm-pre-install coming from layer-docker as a workaround for
|
|
||||||
# offline installs until https://github.com/juju/charm-tools/issues/301 is fixed.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
# Overview
|
|
||||||
|
|
||||||
This interface layer handles the communication with Etcd via the `etcd`
|
|
||||||
interface.
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
## Requires
|
|
||||||
|
|
||||||
This interface layer will set the following states, as appropriate:
|
|
||||||
|
|
||||||
* `{relation_name}.connected` The relation is established, but Etcd may not
|
|
||||||
yet have provided any connection or service information.
|
|
||||||
|
|
||||||
* `{relation_name}.available` Etcd has provided its connection string
|
|
||||||
information, and is ready to serve as a KV store.
|
|
||||||
The provided information can be accessed via the following methods:
|
|
||||||
* `etcd.get_connection_string()`
|
|
||||||
* `etcd.get_version()`
|
|
||||||
* `{relation_name}.tls.available` Etcd has provided the connection string
|
|
||||||
information, and the tls client credentials to communicate with it.
|
|
||||||
The client credentials can be accessed via:
|
|
||||||
* `{relation_name}.get_client_credentials()` returning a dictionary of
|
|
||||||
the clinet certificate, key and CA.
|
|
||||||
* `{relation_name}.save_client_credentials(key, cert, ca)` is a convenience
|
|
||||||
method to save the client certificate, key and CA to files of your
|
|
||||||
choosing.
|
|
||||||
|
|
||||||
|
|
||||||
For example, a common application for this is configuring an applications
|
|
||||||
backend key/value storage, like Docker.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@when('etcd.available', 'docker.available')
|
|
||||||
def swarm_etcd_cluster_setup(etcd):
|
|
||||||
con_string = etcd.connection_string().replace('http', 'etcd')
|
|
||||||
opts = {}
|
|
||||||
opts['connection_string'] = con_string
|
|
||||||
render('docker-compose.yml', 'files/swarm/docker-compose.yml', opts)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Provides
|
|
||||||
|
|
||||||
A charm providing this interface is providing the Etcd rest api service.
|
|
||||||
|
|
||||||
This interface layer will set the following states, as appropriate:
|
|
||||||
|
|
||||||
* `{relation_name}.connected` One or more clients of any type have
|
|
||||||
been related. The charm should call the following methods to provide the
|
|
||||||
appropriate information to the clients:
|
|
||||||
|
|
||||||
* `{relation_name}.set_connection_string(string, version)`
|
|
||||||
* `{relation_name}.set_client_credentials(key, cert, ca)`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@when('db.connected')
|
|
||||||
def send_connection_details(db):
|
|
||||||
cert = leader_get('client_certificate')
|
|
||||||
key = leader_get('client_key')
|
|
||||||
ca = leader_get('certificate_authority')
|
|
||||||
# Set the key, cert, and ca on the db relation
|
|
||||||
db.set_client_credentials(key, cert, ca)
|
|
||||||
|
|
||||||
port = hookenv.config().get('port')
|
|
||||||
# Get all the peers participating in the cluster relation.
|
|
||||||
addresses = cluster.get_peer_addresses()
|
|
||||||
connections = []
|
|
||||||
for address in addresses:
|
|
||||||
connections.append('http://{0}:{1}'.format(address, port))
|
|
||||||
# Set the connection string on the db relation.
|
|
||||||
db.set_connection_string(','.join(conections))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# Contact Information
|
|
||||||
|
|
||||||
### Maintainer
|
|
||||||
- Charles Butler <charles.butler@canonical.com>
|
|
||||||
|
|
||||||
|
|
||||||
# Etcd
|
|
||||||
|
|
||||||
- [Etcd](https://coreos.com/etcd/) home page
|
|
||||||
- [Etcd bug trackers](https://github.com/coreos/etcd/issues)
|
|
||||||
- [Etcd Juju Charm](http://jujucharms.com/?text=etcd)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
name: etcd
|
|
||||||
summary: Interface for relating to ETCD
|
|
||||||
version: 2
|
|
||||||
maintainer: "Charles Butler <charles.butler@canonical.com>"
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from charms.reactive import RelationBase
|
|
||||||
from charms.reactive import hook
|
|
||||||
from charms.reactive import scopes
|
|
||||||
|
|
||||||
|
|
||||||
class EtcdPeer(RelationBase):
|
|
||||||
'''This class handles peer relation communication by setting states that
|
|
||||||
the reactive code can respond to. '''
|
|
||||||
|
|
||||||
scope = scopes.UNIT
|
|
||||||
|
|
||||||
@hook('{peers:etcd}-relation-joined')
|
|
||||||
def peer_joined(self):
|
|
||||||
'''A new peer has joined, set the state on the unit so we can track
|
|
||||||
when they are departed. '''
|
|
||||||
conv = self.conversation()
|
|
||||||
conv.set_state('{relation_name}.joined')
|
|
||||||
|
|
||||||
@hook('{peers:etcd}-relation-departed')
|
|
||||||
def peers_going_away(self):
|
|
||||||
'''Trigger a state on the unit that it is leaving. We can use this
|
|
||||||
state in conjunction with the joined state to determine which unit to
|
|
||||||
unregister from the etcd cluster. '''
|
|
||||||
conv = self.conversation()
|
|
||||||
conv.remove_state('{relation_name}.joined')
|
|
||||||
conv.set_state('{relation_name}.departing')
|
|
||||||
|
|
||||||
def dismiss(self):
|
|
||||||
'''Remove the departing state from all other units in the conversation,
|
|
||||||
and we can resume normal operation.
|
|
||||||
'''
|
|
||||||
for conv in self.conversations():
|
|
||||||
conv.remove_state('{relation_name}.departing')
|
|
||||||
|
|
||||||
def get_peers(self):
|
|
||||||
'''Return a list of names for the peers participating in this
|
|
||||||
conversation scope. '''
|
|
||||||
peers = []
|
|
||||||
# Iterate over all the conversations of this type.
|
|
||||||
for conversation in self.conversations():
|
|
||||||
peers.append(conversation.scope)
|
|
||||||
return peers
|
|
||||||
|
|
||||||
def set_db_ingress_address(self, address):
|
|
||||||
'''Set the ingress address belonging to the db relation.'''
|
|
||||||
for conversation in self.conversations():
|
|
||||||
conversation.set_remote('db-ingress-address', address)
|
|
||||||
|
|
||||||
def get_db_ingress_addresses(self):
|
|
||||||
'''Return a list of db ingress addresses'''
|
|
||||||
addresses = []
|
|
||||||
# Iterate over all the conversations of this type.
|
|
||||||
for conversation in self.conversations():
|
|
||||||
address = conversation.get_remote('db-ingress-address')
|
|
||||||
if address:
|
|
||||||
addresses.append(address)
|
|
||||||
return addresses
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from charms.reactive import RelationBase
|
|
||||||
from charms.reactive import hook
|
|
||||||
from charms.reactive import scopes
|
|
||||||
|
|
||||||
|
|
||||||
class EtcdProvider(RelationBase):
|
|
||||||
scope = scopes.GLOBAL
|
|
||||||
|
|
||||||
@hook('{provides:etcd}-relation-{joined,changed}')
|
|
||||||
def joined_or_changed(self):
|
|
||||||
''' Set the connected state from the provides side of the relation. '''
|
|
||||||
self.set_state('{relation_name}.connected')
|
|
||||||
|
|
||||||
@hook('{provides:etcd}-relation-{broken,departed}')
|
|
||||||
def broken_or_departed(self):
|
|
||||||
'''Remove connected state from the provides side of the relation. '''
|
|
||||||
conv = self.conversation()
|
|
||||||
if len(conv.units) == 1:
|
|
||||||
conv.remove_state('{relation_name}.connected')
|
|
||||||
|
|
||||||
def set_client_credentials(self, key, cert, ca):
|
|
||||||
''' Set the client credentials on the global conversation for this
|
|
||||||
relation. '''
|
|
||||||
self.set_remote('client_key', key)
|
|
||||||
self.set_remote('client_ca', ca)
|
|
||||||
self.set_remote('client_cert', cert)
|
|
||||||
|
|
||||||
def set_connection_string(self, connection_string, version=''):
|
|
||||||
''' Set the connection string on the global conversation for this
|
|
||||||
relation. '''
|
|
||||||
# Note: Version added as a late-dependency for 2 => 3 migration
|
|
||||||
# If no version is specified, consumers should presume etcd 2.x
|
|
||||||
self.set_remote('connection_string', connection_string)
|
|
||||||
self.set_remote('version', version)
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from charms.reactive import RelationBase
|
|
||||||
from charms.reactive import hook
|
|
||||||
from charms.reactive import scopes
|
|
||||||
|
|
||||||
|
|
||||||
class EtcdClient(RelationBase):
|
|
||||||
scope = scopes.GLOBAL
|
|
||||||
|
|
||||||
@hook('{requires:etcd}-relation-{joined,changed}')
|
|
||||||
def changed(self):
|
|
||||||
''' Indicate the relation is connected, and if the relation data is
|
|
||||||
set it is also available. '''
|
|
||||||
self.set_state('{relation_name}.connected')
|
|
||||||
|
|
||||||
if self.get_connection_string():
|
|
||||||
self.set_state('{relation_name}.available')
|
|
||||||
# Get the ca, key, cert from the relation data.
|
|
||||||
cert = self.get_client_credentials()
|
|
||||||
# The tls state depends on the existance of the ca, key and cert.
|
|
||||||
if cert['client_cert'] and cert['client_key'] and cert['client_ca']: # noqa
|
|
||||||
self.set_state('{relation_name}.tls.available')
|
|
||||||
|
|
||||||
@hook('{requires:etcd}-relation-{broken, departed}')
|
|
||||||
def broken(self):
|
|
||||||
''' Indicate the relation is no longer available and not connected. '''
|
|
||||||
self.remove_state('{relation_name}.available')
|
|
||||||
self.remove_state('{relation_name}.connected')
|
|
||||||
self.remove_state('{relation_name}.tls.available')
|
|
||||||
|
|
||||||
def connection_string(self):
|
|
||||||
''' This method is depreciated but ensures backward compatibility
|
|
||||||
@see get_connection_string(self). '''
|
|
||||||
return self.get_connection_string()
|
|
||||||
|
|
||||||
def get_connection_string(self):
|
|
||||||
''' Return the connection string, if available, or None. '''
|
|
||||||
return self.get_remote('connection_string')
|
|
||||||
|
|
||||||
def get_version(self):
|
|
||||||
''' Return the version of the etd protocol being used, or None. '''
|
|
||||||
return self.get_remote('version')
|
|
||||||
|
|
||||||
def get_client_credentials(self):
|
|
||||||
''' Return a dict with the client certificate, ca and key to
|
|
||||||
communicate with etcd using tls. '''
|
|
||||||
return {'client_cert': self.get_remote('client_cert'),
|
|
||||||
'client_key': self.get_remote('client_key'),
|
|
||||||
'client_ca': self.get_remote('client_ca')}
|
|
||||||
|
|
||||||
def save_client_credentials(self, key, cert, ca):
|
|
||||||
''' Save all the client certificates for etcd to local files. '''
|
|
||||||
self._save_remote_data('client_cert', cert)
|
|
||||||
self._save_remote_data('client_key', key)
|
|
||||||
self._save_remote_data('client_ca', ca)
|
|
||||||
|
|
||||||
def _save_remote_data(self, key, path):
|
|
||||||
''' Save the remote data to a file indicated by path creating the
|
|
||||||
parent directory if needed.'''
|
|
||||||
value = self.get_remote(key)
|
|
||||||
if value:
|
|
||||||
parent = os.path.dirname(path)
|
|
||||||
if not os.path.isdir(parent):
|
|
||||||
os.makedirs(parent)
|
|
||||||
with open(path, 'w') as stream:
|
|
||||||
stream.write(value)
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
name: Test Suite for K8s Service Interface
|
|
||||||
|
|
||||||
on:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-and-unit-tests:
|
|
||||||
name: Lint & Unit tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python: [3.6, 3.7, 3.8, 3.9]
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python }}
|
|
||||||
- name: Install Tox
|
|
||||||
run: pip install tox
|
|
||||||
- name: Run lint & unit tests
|
|
||||||
run: tox
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
.tox
|
|
||||||
__pycache__
|
|
||||||
*.pyc
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
name: kubernetes-cni
|
|
||||||
summary: Interface for relating various CNI implementations
|
|
||||||
version: 0
|
|
||||||
maintainer: "George Kraft <george.kraft@canonical.com>"
|
|
||||||
ignore:
|
|
||||||
- tests
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
|
||||||
from charmhelpers.core.host import file_hash
|
|
||||||
from charms.layer.kubernetes_common import kubeclientconfig_path
|
|
||||||
from charms.reactive import Endpoint
|
|
||||||
from charms.reactive import toggle_flag, clear_flag
|
|
||||||
|
|
||||||
|
|
||||||
class CNIPluginProvider(Endpoint):
|
|
||||||
def manage_flags(self):
|
|
||||||
toggle_flag(self.expand_name("{endpoint_name}.connected"), self.is_joined)
|
|
||||||
toggle_flag(
|
|
||||||
self.expand_name("{endpoint_name}.available"), self.config_available()
|
|
||||||
)
|
|
||||||
clear_flag(self.expand_name("endpoint.{endpoint_name}.changed"))
|
|
||||||
|
|
||||||
def config_available(self):
|
|
||||||
"""Ensures all config from the CNI plugin is available."""
|
|
||||||
goal_state = hookenv.goal_state()
|
|
||||||
related_apps = [
|
|
||||||
app
|
|
||||||
for app in goal_state.get("relations", {}).get(self.endpoint_name, "")
|
|
||||||
if "/" not in app
|
|
||||||
]
|
|
||||||
if not related_apps:
|
|
||||||
return False
|
|
||||||
configs = self.get_configs()
|
|
||||||
return all(
|
|
||||||
"cidr" in config and "cni-conf-file" in config
|
|
||||||
for config in [configs.get(related_app, {}) for related_app in related_apps]
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_config(self, default=None):
|
|
||||||
"""Get CNI config for one related application.
|
|
||||||
|
|
||||||
If default is specified, and there is a related application with a
|
|
||||||
matching name, then that application is chosen. Otherwise, the
|
|
||||||
application is chosen alphabetically.
|
|
||||||
|
|
||||||
Whichever application is chosen, that application's CNI config is
|
|
||||||
returned.
|
|
||||||
"""
|
|
||||||
configs = self.get_configs()
|
|
||||||
if not configs:
|
|
||||||
return {}
|
|
||||||
elif default and default not in configs:
|
|
||||||
msg = "relation not found for default CNI %s, ignoring" % default
|
|
||||||
hookenv.log(msg, level="WARN")
|
|
||||||
return self.get_config()
|
|
||||||
elif default:
|
|
||||||
return configs.get(default, {})
|
|
||||||
else:
|
|
||||||
return configs.get(sorted(configs)[0], {})
|
|
||||||
|
|
||||||
def get_configs(self):
|
|
||||||
"""Get CNI configs for all related applications.
|
|
||||||
|
|
||||||
This returns a mapping of application names to CNI configs. Here's an
|
|
||||||
example return value:
|
|
||||||
{
|
|
||||||
'flannel': {
|
|
||||||
'cidr': '10.1.0.0/16',
|
|
||||||
'cni-conf-file': '10-flannel.conflist'
|
|
||||||
},
|
|
||||||
'calico': {
|
|
||||||
'cidr': '192.168.0.0/16',
|
|
||||||
'cni-conf-file': '10-calico.conflist'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
relation.application_name: relation.joined_units.received_raw
|
|
||||||
for relation in self.relations
|
|
||||||
if relation.application_name
|
|
||||||
}
|
|
||||||
|
|
||||||
def notify_kubeconfig_changed(self):
|
|
||||||
kubeconfig_hash = file_hash(kubeclientconfig_path)
|
|
||||||
for relation in self.relations:
|
|
||||||
relation.to_publish_raw.update({"kubeconfig-hash": kubeconfig_hash})
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from charmhelpers.core import unitdata
|
|
||||||
from charms.reactive import Endpoint
|
|
||||||
from charms.reactive import when_any, when_not
|
|
||||||
from charms.reactive import set_state, remove_state
|
|
||||||
|
|
||||||
db = unitdata.kv()
|
|
||||||
|
|
||||||
|
|
||||||
class CNIPluginClient(Endpoint):
|
|
||||||
def manage_flags(self):
|
|
||||||
kubeconfig_hash = self.get_config().get("kubeconfig-hash")
|
|
||||||
kubeconfig_hash_key = self.expand_name("{endpoint_name}.kubeconfig-hash")
|
|
||||||
if kubeconfig_hash:
|
|
||||||
set_state(self.expand_name("{endpoint_name}.kubeconfig.available"))
|
|
||||||
if kubeconfig_hash != db.get(kubeconfig_hash_key):
|
|
||||||
set_state(self.expand_name("{endpoint_name}.kubeconfig.changed"))
|
|
||||||
db.set(kubeconfig_hash_key, kubeconfig_hash)
|
|
||||||
|
|
||||||
@when_any("endpoint.{endpoint_name}.joined", "endpoint.{endpoint_name}.changed")
|
|
||||||
def changed(self):
|
|
||||||
"""Indicate the relation is connected, and if the relation data is
|
|
||||||
set it is also available."""
|
|
||||||
set_state(self.expand_name("{endpoint_name}.connected"))
|
|
||||||
remove_state(self.expand_name("endpoint.{endpoint_name}.changed"))
|
|
||||||
|
|
||||||
@when_not("endpoint.{endpoint_name}.joined")
|
|
||||||
def broken(self):
|
|
||||||
"""Indicate the relation is no longer available and not connected."""
|
|
||||||
remove_state(self.expand_name("{endpoint_name}.connected"))
|
|
||||||
|
|
||||||
def get_config(self):
|
|
||||||
"""Get the kubernetes configuration information."""
|
|
||||||
return self.all_joined_units.received_raw
|
|
||||||
|
|
||||||
def set_config(self, cidr, cni_conf_file):
|
|
||||||
"""Sets the CNI configuration information."""
|
|
||||||
for relation in self.relations:
|
|
||||||
relation.to_publish_raw.update(
|
|
||||||
{"cidr": cidr, "cni-conf-file": cni_conf_file}
|
|
||||||
)
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
[tox]
|
|
||||||
skipsdist = True
|
|
||||||
envlist = lint,py3
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
basepython = python3
|
|
||||||
setenv =
|
|
||||||
PYTHONPATH={toxinidir}:{toxinidir}/lib
|
|
||||||
PYTHONBREAKPOINT=ipdb.set_trace
|
|
||||||
deps =
|
|
||||||
pyyaml
|
|
||||||
pytest
|
|
||||||
flake8
|
|
||||||
black
|
|
||||||
ipdb
|
|
||||||
charms.unit_test
|
|
||||||
commands = pytest --tb native -s {posargs}
|
|
||||||
|
|
||||||
[testenv:lint]
|
|
||||||
envdir = {toxworkdir}/py3
|
|
||||||
commands =
|
|
||||||
flake8 {toxinidir}
|
|
||||||
black --check {toxinidir}
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
exclude=.tox
|
|
||||||
max-line-length = 88
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer import basic # noqa
|
|
||||||
basic.bootstrap_charm_deps()
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv # noqa
|
|
||||||
hookenv.atstart(basic.init_config_states)
|
|
||||||
hookenv.atexit(basic.clear_config_states)
|
|
||||||
|
|
||||||
|
|
||||||
# This will load and run the appropriate @hook and other decorated
|
|
||||||
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
|
|
||||||
# and $JUJU_CHARM_DIR/hooks/relations.
|
|
||||||
#
|
|
||||||
# See https://jujucharms.com/docs/stable/authors-charm-building
|
|
||||||
# for more information on this pattern.
|
|
||||||
from charms.reactive import main # noqa
|
|
||||||
main()
|
|
||||||
1378
calico/icon.svg
1378
calico/icon.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 102 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
"includes":
|
|
||||||
- "layer:options"
|
|
||||||
- "interface:etcd"
|
|
||||||
- "interface:kubernetes-cni"
|
|
||||||
- "layer:basic"
|
|
||||||
- "layer:leadership"
|
|
||||||
- "layer:status"
|
|
||||||
- "layer:kubernetes-common"
|
|
||||||
"exclude": [".travis.yml", "tests", "tox.ini", "test-requirements.txt", "unit_tests"]
|
|
||||||
"options":
|
|
||||||
"basic":
|
|
||||||
"packages": []
|
|
||||||
"python_packages": []
|
|
||||||
"use_venv": !!bool "true"
|
|
||||||
"include_system_packages": !!bool "false"
|
|
||||||
"leadership": {}
|
|
||||||
"status":
|
|
||||||
"patch-hookenv": !!bool "true"
|
|
||||||
"kubernetes-common": {}
|
|
||||||
"calico": {}
|
|
||||||
"repo": "https://github.com/juju-solutions/layer-calico.git"
|
|
||||||
"is": "calico"
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
from subprocess import check_output
|
|
||||||
|
|
||||||
|
|
||||||
def arch():
|
|
||||||
'''Return the package architecture as a string.'''
|
|
||||||
# Get the package architecture for this system.
|
|
||||||
architecture = check_output(['dpkg', '--print-architecture']).rstrip()
|
|
||||||
# Convert the binary result into a string.
|
|
||||||
architecture = architecture.decode('utf-8')
|
|
||||||
return architecture
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import sys
|
|
||||||
from importlib import import_module
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def import_layer_libs():
|
|
||||||
"""
|
|
||||||
Ensure that all layer libraries are imported.
|
|
||||||
|
|
||||||
This makes it possible to do the following:
|
|
||||||
|
|
||||||
from charms import layer
|
|
||||||
|
|
||||||
layer.foo.do_foo_thing()
|
|
||||||
|
|
||||||
Note: This function must be called after bootstrap.
|
|
||||||
"""
|
|
||||||
for module_file in Path('lib/charms/layer').glob('*'):
|
|
||||||
module_name = module_file.stem
|
|
||||||
if module_name in ('__init__', 'basic', 'execd') or not (
|
|
||||||
module_file.suffix == '.py' or module_file.is_dir()
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
import_module('charms.layer.{}'.format(module_name))
|
|
||||||
|
|
||||||
|
|
||||||
# Terrible hack to support the old terrible interface.
|
|
||||||
# Try to get people to call layer.options.get() instead so
|
|
||||||
# that we can remove this garbage.
|
|
||||||
# Cribbed from https://stackoverfLow.com/a/48100440/4941864
|
|
||||||
class OptionsBackwardsCompatibilityHack(sys.modules[__name__].__class__):
|
|
||||||
def __call__(self, section=None, layer_file=None):
|
|
||||||
if layer_file is None:
|
|
||||||
return self.get(section=section)
|
|
||||||
else:
|
|
||||||
return self.get(section=section,
|
|
||||||
layer_file=Path(layer_file))
|
|
||||||
|
|
||||||
|
|
||||||
def patch_options_interface():
|
|
||||||
from charms.layer import options
|
|
||||||
if sys.version_info.minor >= 5:
|
|
||||||
options.__class__ = OptionsBackwardsCompatibilityHack
|
|
||||||
else:
|
|
||||||
# Py 3.4 doesn't support changing the __class__, so we have to do it
|
|
||||||
# another way. The last line is needed because we already have a
|
|
||||||
# reference that doesn't get updated with sys.modules.
|
|
||||||
name = options.__name__
|
|
||||||
hack = OptionsBackwardsCompatibilityHack(name)
|
|
||||||
hack.get = options.get
|
|
||||||
sys.modules[name] = hack
|
|
||||||
sys.modules[__name__].options = hack
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
patch_options_interface()
|
|
||||||
except ImportError:
|
|
||||||
# This may fail if pyyaml hasn't been installed yet. But in that
|
|
||||||
# case, the bootstrap logic will try it again once it has.
|
|
||||||
pass
|
|
||||||
|
|
@ -1,508 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from pkg_resources import Requirement
|
|
||||||
from glob import glob
|
|
||||||
from subprocess import check_call, check_output, CalledProcessError
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
from charms import layer
|
|
||||||
from charms.layer.execd import execd_preinstall
|
|
||||||
|
|
||||||
|
|
||||||
def _get_subprocess_env():
|
|
||||||
env = os.environ.copy()
|
|
||||||
env['LANG'] = env.get('LANG', 'C.UTF-8')
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def get_series():
|
|
||||||
"""
|
|
||||||
Return series for a few known OS:es.
|
|
||||||
Tested as of 2019 november:
|
|
||||||
* centos6, centos7, rhel6.
|
|
||||||
* bionic
|
|
||||||
"""
|
|
||||||
series = ""
|
|
||||||
|
|
||||||
# Looking for content in /etc/os-release
|
|
||||||
# works for ubuntu + some centos
|
|
||||||
if os.path.isfile('/etc/os-release'):
|
|
||||||
d = {}
|
|
||||||
with open('/etc/os-release', 'r') as rel:
|
|
||||||
for l in rel:
|
|
||||||
if not re.match(r'^\s*$', l):
|
|
||||||
k, v = l.split('=')
|
|
||||||
d[k.strip()] = v.strip().replace('"', '')
|
|
||||||
series = "{ID}{VERSION_ID}".format(**d)
|
|
||||||
|
|
||||||
# Looking for content in /etc/redhat-release
|
|
||||||
# works for redhat enterprise systems
|
|
||||||
elif os.path.isfile('/etc/redhat-release'):
|
|
||||||
with open('/etc/redhat-release', 'r') as redhatlsb:
|
|
||||||
# CentOS Linux release 7.7.1908 (Core)
|
|
||||||
line = redhatlsb.readline()
|
|
||||||
release = int(line.split("release")[1].split()[0][0])
|
|
||||||
series = "centos" + str(release)
|
|
||||||
|
|
||||||
# Looking for content in /etc/lsb-release
|
|
||||||
# works for ubuntu
|
|
||||||
elif os.path.isfile('/etc/lsb-release'):
|
|
||||||
d = {}
|
|
||||||
with open('/etc/lsb-release', 'r') as lsb:
|
|
||||||
for l in lsb:
|
|
||||||
k, v = l.split('=')
|
|
||||||
d[k.strip()] = v.strip()
|
|
||||||
series = d['DISTRIB_CODENAME']
|
|
||||||
|
|
||||||
# This is what happens if we cant figure out the OS.
|
|
||||||
else:
|
|
||||||
series = "unknown"
|
|
||||||
return series
|
|
||||||
|
|
||||||
|
|
||||||
def bootstrap_charm_deps():
|
|
||||||
"""
|
|
||||||
Set up the base charm dependencies so that the reactive system can run.
|
|
||||||
"""
|
|
||||||
# execd must happen first, before any attempt to install packages or
|
|
||||||
# access the network, because sites use this hook to do bespoke
|
|
||||||
# configuration and install secrets so the rest of this bootstrap
|
|
||||||
# and the charm itself can actually succeed. This call does nothing
|
|
||||||
# unless the operator has created and populated $JUJU_CHARM_DIR/exec.d.
|
|
||||||
execd_preinstall()
|
|
||||||
# ensure that $JUJU_CHARM_DIR/bin is on the path, for helper scripts
|
|
||||||
|
|
||||||
series = get_series()
|
|
||||||
|
|
||||||
# OMG?! is build-essentials needed?
|
|
||||||
ubuntu_packages = ['python3-pip',
|
|
||||||
'python3-setuptools',
|
|
||||||
'python3-yaml',
|
|
||||||
'python3-dev',
|
|
||||||
'python3-wheel',
|
|
||||||
'build-essential']
|
|
||||||
|
|
||||||
# I'm not going to "yum group info "Development Tools"
|
|
||||||
# omitting above madness
|
|
||||||
centos_packages = ['python3-pip',
|
|
||||||
'python3-setuptools',
|
|
||||||
'python3-devel',
|
|
||||||
'python3-wheel']
|
|
||||||
|
|
||||||
packages_needed = []
|
|
||||||
if 'centos' in series:
|
|
||||||
packages_needed = centos_packages
|
|
||||||
else:
|
|
||||||
packages_needed = ubuntu_packages
|
|
||||||
|
|
||||||
charm_dir = os.environ['JUJU_CHARM_DIR']
|
|
||||||
os.environ['PATH'] += ':%s' % os.path.join(charm_dir, 'bin')
|
|
||||||
venv = os.path.abspath('../.venv')
|
|
||||||
vbin = os.path.join(venv, 'bin')
|
|
||||||
vpip = os.path.join(vbin, 'pip')
|
|
||||||
vpy = os.path.join(vbin, 'python')
|
|
||||||
hook_name = os.path.basename(sys.argv[0])
|
|
||||||
is_bootstrapped = os.path.exists('wheelhouse/.bootstrapped')
|
|
||||||
is_charm_upgrade = hook_name == 'upgrade-charm'
|
|
||||||
is_series_upgrade = hook_name == 'post-series-upgrade'
|
|
||||||
is_post_upgrade = os.path.exists('wheelhouse/.upgraded')
|
|
||||||
is_upgrade = (not is_post_upgrade and
|
|
||||||
(is_charm_upgrade or is_series_upgrade))
|
|
||||||
if is_bootstrapped and not is_upgrade:
|
|
||||||
# older subordinates might have downgraded charm-env, so we should
|
|
||||||
# restore it if necessary
|
|
||||||
install_or_update_charm_env()
|
|
||||||
activate_venv()
|
|
||||||
# the .upgrade file prevents us from getting stuck in a loop
|
|
||||||
# when re-execing to activate the venv; at this point, we've
|
|
||||||
# activated the venv, so it's safe to clear it
|
|
||||||
if is_post_upgrade:
|
|
||||||
os.unlink('wheelhouse/.upgraded')
|
|
||||||
return
|
|
||||||
if os.path.exists(venv):
|
|
||||||
try:
|
|
||||||
# focal installs or upgrades prior to PR 160 could leave the venv
|
|
||||||
# in a broken state which would prevent subsequent charm upgrades
|
|
||||||
_load_installed_versions(vpip)
|
|
||||||
except CalledProcessError:
|
|
||||||
is_broken_venv = True
|
|
||||||
else:
|
|
||||||
is_broken_venv = False
|
|
||||||
if is_upgrade or is_broken_venv:
|
|
||||||
# All upgrades should do a full clear of the venv, rather than
|
|
||||||
# just updating it, to bring in updates to Python itself
|
|
||||||
shutil.rmtree(venv)
|
|
||||||
if is_upgrade:
|
|
||||||
if os.path.exists('wheelhouse/.bootstrapped'):
|
|
||||||
os.unlink('wheelhouse/.bootstrapped')
|
|
||||||
# bootstrap wheelhouse
|
|
||||||
if os.path.exists('wheelhouse'):
|
|
||||||
pre_eoan = series in ('ubuntu12.04', 'precise',
|
|
||||||
'ubuntu14.04', 'trusty',
|
|
||||||
'ubuntu16.04', 'xenial',
|
|
||||||
'ubuntu18.04', 'bionic')
|
|
||||||
pydistutils_lines = [
|
|
||||||
"[easy_install]\n",
|
|
||||||
"find_links = file://{}/wheelhouse/\n".format(charm_dir),
|
|
||||||
"no_index=True\n",
|
|
||||||
"index_url=\n", # deliberately nothing here; disables it.
|
|
||||||
]
|
|
||||||
if pre_eoan:
|
|
||||||
pydistutils_lines.append("allow_hosts = ''\n")
|
|
||||||
with open('/root/.pydistutils.cfg', 'w') as fp:
|
|
||||||
# make sure that easy_install also only uses the wheelhouse
|
|
||||||
# (see https://github.com/pypa/pip/issues/410)
|
|
||||||
fp.writelines(pydistutils_lines)
|
|
||||||
if 'centos' in series:
|
|
||||||
yum_install(packages_needed)
|
|
||||||
else:
|
|
||||||
apt_install(packages_needed)
|
|
||||||
from charms.layer import options
|
|
||||||
cfg = options.get('basic')
|
|
||||||
# include packages defined in layer.yaml
|
|
||||||
if 'centos' in series:
|
|
||||||
yum_install(cfg.get('packages', []))
|
|
||||||
else:
|
|
||||||
apt_install(cfg.get('packages', []))
|
|
||||||
# if we're using a venv, set it up
|
|
||||||
if cfg.get('use_venv'):
|
|
||||||
if not os.path.exists(venv):
|
|
||||||
series = get_series()
|
|
||||||
if series in ('ubuntu12.04', 'precise',
|
|
||||||
'ubuntu14.04', 'trusty'):
|
|
||||||
apt_install(['python-virtualenv'])
|
|
||||||
elif 'centos' in series:
|
|
||||||
yum_install(['python-virtualenv'])
|
|
||||||
else:
|
|
||||||
apt_install(['virtualenv'])
|
|
||||||
cmd = ['virtualenv', '-ppython3', '--never-download', venv]
|
|
||||||
if cfg.get('include_system_packages'):
|
|
||||||
cmd.append('--system-site-packages')
|
|
||||||
check_call(cmd, env=_get_subprocess_env())
|
|
||||||
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
|
|
||||||
pip = vpip
|
|
||||||
else:
|
|
||||||
pip = 'pip3'
|
|
||||||
# save a copy of system pip to prevent `pip3 install -U pip`
|
|
||||||
# from changing it
|
|
||||||
if os.path.exists('/usr/bin/pip'):
|
|
||||||
shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
|
|
||||||
pre_install_pkgs = ['pip', 'setuptools', 'setuptools-scm']
|
|
||||||
# we bundle these packages to work around bugs in older versions (such
|
|
||||||
# as https://github.com/pypa/pip/issues/56), but if the system already
|
|
||||||
# provided a newer version, downgrading it can cause other problems
|
|
||||||
_update_if_newer(pip, pre_install_pkgs)
|
|
||||||
# install the rest of the wheelhouse deps (extract the pkg names into
|
|
||||||
# a set so that we can ignore the pre-install packages and let pip
|
|
||||||
# choose the best version in case there are multiple from layer
|
|
||||||
# conflicts)
|
|
||||||
_versions = _load_wheelhouse_versions()
|
|
||||||
_pkgs = _versions.keys() - set(pre_install_pkgs)
|
|
||||||
# Jinja2 3+ relies on MarkupSafe actually being installed prior to
|
|
||||||
# attempting to be installed from the wheelhouse. Thus, if MarkupSafe
|
|
||||||
# and/or wheel are in _pkgs, then install them first.
|
|
||||||
_pre_packages = [p for p in _pkgs if p in ('wheel', 'MarkupSafe')]
|
|
||||||
_pkgs = [p for p in _pkgs if p not in _pre_packages]
|
|
||||||
for _pkgs_set in (_pre_packages, _pkgs):
|
|
||||||
# add back the versions such that each package in pkgs is
|
|
||||||
# <package_name>==<version>.
|
|
||||||
# This ensures that pip 20.3.4+ will install the packages from the
|
|
||||||
# wheelhouse without (erroneously) flagging an error.
|
|
||||||
pkgs = _add_back_versions(_pkgs_set, _versions)
|
|
||||||
reinstall_flag = '--force-reinstall'
|
|
||||||
# if not cfg.get('use_venv', True) and pre_eoan:
|
|
||||||
if not cfg.get('use_venv', True):
|
|
||||||
reinstall_flag = '--ignore-installed'
|
|
||||||
check_call([pip, 'install', '-U', reinstall_flag, '--no-index',
|
|
||||||
'--no-cache-dir', '-f', 'wheelhouse'] + list(pkgs),
|
|
||||||
env=_get_subprocess_env())
|
|
||||||
# re-enable installation from pypi
|
|
||||||
os.remove('/root/.pydistutils.cfg')
|
|
||||||
|
|
||||||
# install pyyaml for centos7, since, unlike the ubuntu image, the
|
|
||||||
# default image for centos doesn't include pyyaml; see the discussion:
|
|
||||||
# https://discourse.jujucharms.com/t/charms-for-centos-lets-begin
|
|
||||||
if 'centos' in series:
|
|
||||||
check_call([pip, 'install', '-U', 'pyyaml'],
|
|
||||||
env=_get_subprocess_env())
|
|
||||||
|
|
||||||
# install python packages from layer options
|
|
||||||
if cfg.get('python_packages'):
|
|
||||||
check_call([pip, 'install', '-U'] + cfg.get('python_packages'),
|
|
||||||
env=_get_subprocess_env())
|
|
||||||
if not cfg.get('use_venv'):
|
|
||||||
# restore system pip to prevent `pip3 install -U pip`
|
|
||||||
# from changing it
|
|
||||||
if os.path.exists('/usr/bin/pip.save'):
|
|
||||||
shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
|
|
||||||
os.remove('/usr/bin/pip.save')
|
|
||||||
# setup wrappers to ensure envs are used for scripts
|
|
||||||
install_or_update_charm_env()
|
|
||||||
for wrapper in ('charms.reactive', 'charms.reactive.sh',
|
|
||||||
'chlp', 'layer_option'):
|
|
||||||
src = os.path.join('/usr/local/sbin', 'charm-env')
|
|
||||||
dst = os.path.join('/usr/local/sbin', wrapper)
|
|
||||||
if not os.path.exists(dst):
|
|
||||||
os.symlink(src, dst)
|
|
||||||
if cfg.get('use_venv'):
|
|
||||||
shutil.copy2('bin/layer_option', vbin)
|
|
||||||
else:
|
|
||||||
shutil.copy2('bin/layer_option', '/usr/local/bin/')
|
|
||||||
# re-link the charm copy to the wrapper in case charms
|
|
||||||
# call bin/layer_option directly (as was the old pattern)
|
|
||||||
os.remove('bin/layer_option')
|
|
||||||
os.symlink('/usr/local/sbin/layer_option', 'bin/layer_option')
|
|
||||||
# flag us as having already bootstrapped so we don't do it again
|
|
||||||
open('wheelhouse/.bootstrapped', 'w').close()
|
|
||||||
if is_upgrade:
|
|
||||||
# flag us as having already upgraded so we don't do it again
|
|
||||||
open('wheelhouse/.upgraded', 'w').close()
|
|
||||||
# Ensure that the newly bootstrapped libs are available.
|
|
||||||
# Note: this only seems to be an issue with namespace packages.
|
|
||||||
# Non-namespace-package libs (e.g., charmhelpers) are available
|
|
||||||
# without having to reload the interpreter. :/
|
|
||||||
reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
|
|
||||||
|
|
||||||
|
|
||||||
def _load_installed_versions(pip):
|
|
||||||
pip_freeze = check_output([pip, 'freeze']).decode('utf8')
|
|
||||||
versions = {}
|
|
||||||
for pkg_ver in pip_freeze.splitlines():
|
|
||||||
try:
|
|
||||||
req = Requirement.parse(pkg_ver)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
versions.update({
|
|
||||||
req.project_name: LooseVersion(ver)
|
|
||||||
for op, ver in req.specs if op == '=='
|
|
||||||
})
|
|
||||||
return versions
|
|
||||||
|
|
||||||
|
|
||||||
def _load_wheelhouse_versions():
|
|
||||||
versions = {}
|
|
||||||
for wheel in glob('wheelhouse/*'):
|
|
||||||
pkg, ver = os.path.basename(wheel).rsplit('-', 1)
|
|
||||||
# nb: LooseVersion ignores the file extension
|
|
||||||
versions[pkg.replace('_', '-')] = LooseVersion(ver)
|
|
||||||
return versions
|
|
||||||
|
|
||||||
|
|
||||||
def _add_back_versions(pkgs, versions):
|
|
||||||
"""Add back the version strings to each of the packages.
|
|
||||||
|
|
||||||
The versions are LooseVersion() from _load_wheelhouse_versions(). This
|
|
||||||
function strips the ".zip" or ".tar.gz" from the end of the version string
|
|
||||||
and adds it back to the package in the form of <package_name>==<version>
|
|
||||||
|
|
||||||
If a package name is not a key in the versions dictionary, then it is
|
|
||||||
returned in the list unchanged.
|
|
||||||
|
|
||||||
:param pkgs: A list of package names
|
|
||||||
:type pkgs: List[str]
|
|
||||||
:param versions: A map of package to LooseVersion
|
|
||||||
:type versions: Dict[str, LooseVersion]
|
|
||||||
:returns: A list of (maybe) versioned packages
|
|
||||||
:rtype: List[str]
|
|
||||||
"""
|
|
||||||
def _strip_ext(s):
|
|
||||||
"""Strip an extension (if it exists) from the string
|
|
||||||
|
|
||||||
:param s: the string to strip an extension off if it exists
|
|
||||||
:type s: str
|
|
||||||
:returns: string without an extension of .zip or .tar.gz
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
for ending in [".zip", ".tar.gz"]:
|
|
||||||
if s.endswith(ending):
|
|
||||||
return s[:-len(ending)]
|
|
||||||
return s
|
|
||||||
|
|
||||||
def _maybe_add_version(pkg):
|
|
||||||
"""Maybe add back the version number to a package if it exists.
|
|
||||||
|
|
||||||
Adds the version number, if the package exists in the lexically
|
|
||||||
captured `versions` dictionary, in the form <pkg>==<version>. Strips
|
|
||||||
the extension if it exists.
|
|
||||||
|
|
||||||
:param pkg: the package name to (maybe) add the version number to.
|
|
||||||
:type pkg: str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return "{}=={}".format(pkg, _strip_ext(str(versions[pkg])))
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return pkg
|
|
||||||
|
|
||||||
return [_maybe_add_version(pkg) for pkg in pkgs]
|
|
||||||
|
|
||||||
|
|
||||||
def _update_if_newer(pip, pkgs):
|
|
||||||
installed = _load_installed_versions(pip)
|
|
||||||
wheelhouse = _load_wheelhouse_versions()
|
|
||||||
for pkg in pkgs:
|
|
||||||
if pkg not in installed or wheelhouse[pkg] > installed[pkg]:
|
|
||||||
check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
|
|
||||||
pkg], env=_get_subprocess_env())
|
|
||||||
|
|
||||||
|
|
||||||
def install_or_update_charm_env():
|
|
||||||
# On Trusty python3-pkg-resources is not installed
|
|
||||||
try:
|
|
||||||
from pkg_resources import parse_version
|
|
||||||
except ImportError:
|
|
||||||
apt_install(['python3-pkg-resources'])
|
|
||||||
from pkg_resources import parse_version
|
|
||||||
|
|
||||||
try:
|
|
||||||
installed_version = parse_version(
|
|
||||||
check_output(['/usr/local/sbin/charm-env',
|
|
||||||
'--version']).decode('utf8'))
|
|
||||||
except (CalledProcessError, FileNotFoundError):
|
|
||||||
installed_version = parse_version('0.0.0')
|
|
||||||
try:
|
|
||||||
bundled_version = parse_version(
|
|
||||||
check_output(['bin/charm-env',
|
|
||||||
'--version']).decode('utf8'))
|
|
||||||
except (CalledProcessError, FileNotFoundError):
|
|
||||||
bundled_version = parse_version('0.0.0')
|
|
||||||
if installed_version < bundled_version:
|
|
||||||
shutil.copy2('bin/charm-env', '/usr/local/sbin/')
|
|
||||||
|
|
||||||
|
|
||||||
def activate_venv():
|
|
||||||
"""
|
|
||||||
Activate the venv if enabled in ``layer.yaml``.
|
|
||||||
|
|
||||||
This is handled automatically for normal hooks, but actions might
|
|
||||||
need to invoke this manually, using something like:
|
|
||||||
|
|
||||||
# Load modules from $JUJU_CHARM_DIR/lib
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib')
|
|
||||||
|
|
||||||
from charms.layer.basic import activate_venv
|
|
||||||
activate_venv()
|
|
||||||
|
|
||||||
This will ensure that modules installed in the charm's
|
|
||||||
virtual environment are available to the action.
|
|
||||||
"""
|
|
||||||
from charms.layer import options
|
|
||||||
venv = os.path.abspath('../.venv')
|
|
||||||
vbin = os.path.join(venv, 'bin')
|
|
||||||
vpy = os.path.join(vbin, 'python')
|
|
||||||
use_venv = options.get('basic', 'use_venv')
|
|
||||||
if use_venv and '.venv' not in sys.executable:
|
|
||||||
# activate the venv
|
|
||||||
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
|
|
||||||
reload_interpreter(vpy)
|
|
||||||
layer.patch_options_interface()
|
|
||||||
layer.import_layer_libs()
|
|
||||||
|
|
||||||
|
|
||||||
def reload_interpreter(python):
|
|
||||||
"""
|
|
||||||
Reload the python interpreter to ensure that all deps are available.
|
|
||||||
|
|
||||||
Newly installed modules in namespace packages sometimes seemt to
|
|
||||||
not be picked up by Python 3.
|
|
||||||
"""
|
|
||||||
os.execve(python, [python] + list(sys.argv), os.environ)
|
|
||||||
|
|
||||||
|
|
||||||
def apt_install(packages):
|
|
||||||
"""
|
|
||||||
Install apt packages.
|
|
||||||
|
|
||||||
This ensures a consistent set of options that are often missed but
|
|
||||||
should really be set.
|
|
||||||
"""
|
|
||||||
if isinstance(packages, (str, bytes)):
|
|
||||||
packages = [packages]
|
|
||||||
|
|
||||||
env = _get_subprocess_env()
|
|
||||||
|
|
||||||
if 'DEBIAN_FRONTEND' not in env:
|
|
||||||
env['DEBIAN_FRONTEND'] = 'noninteractive'
|
|
||||||
|
|
||||||
cmd = ['apt-get',
|
|
||||||
'--option=Dpkg::Options::=--force-confold',
|
|
||||||
'--assume-yes',
|
|
||||||
'install']
|
|
||||||
for attempt in range(3):
|
|
||||||
try:
|
|
||||||
check_call(cmd + packages, env=env)
|
|
||||||
except CalledProcessError:
|
|
||||||
if attempt == 2: # third attempt
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
# sometimes apt-get update needs to be run
|
|
||||||
check_call(['apt-get', 'update'], env=env)
|
|
||||||
except CalledProcessError:
|
|
||||||
# sometimes it's a dpkg lock issue
|
|
||||||
pass
|
|
||||||
sleep(5)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def yum_install(packages):
|
|
||||||
""" Installs packages with yum.
|
|
||||||
This function largely mimics the apt_install function for consistency.
|
|
||||||
"""
|
|
||||||
if packages:
|
|
||||||
env = os.environ.copy()
|
|
||||||
cmd = ['yum', '-y', 'install']
|
|
||||||
for attempt in range(3):
|
|
||||||
try:
|
|
||||||
check_call(cmd + packages, env=env)
|
|
||||||
except CalledProcessError:
|
|
||||||
if attempt == 2:
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
check_call(['yum', 'update'], env=env)
|
|
||||||
except CalledProcessError:
|
|
||||||
pass
|
|
||||||
sleep(5)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def init_config_states():
|
|
||||||
import yaml
|
|
||||||
from charmhelpers.core import hookenv
|
|
||||||
from charms.reactive import set_state
|
|
||||||
from charms.reactive import toggle_state
|
|
||||||
config = hookenv.config()
|
|
||||||
config_defaults = {}
|
|
||||||
config_defs = {}
|
|
||||||
config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
|
|
||||||
if os.path.exists(config_yaml):
|
|
||||||
with open(config_yaml) as fp:
|
|
||||||
config_defs = yaml.safe_load(fp).get('options', {})
|
|
||||||
config_defaults = {key: value.get('default')
|
|
||||||
for key, value in config_defs.items()}
|
|
||||||
for opt in config_defs.keys():
|
|
||||||
if config.changed(opt):
|
|
||||||
set_state('config.changed')
|
|
||||||
set_state('config.changed.{}'.format(opt))
|
|
||||||
toggle_state('config.set.{}'.format(opt), config.get(opt))
|
|
||||||
toggle_state('config.default.{}'.format(opt),
|
|
||||||
config.get(opt) == config_defaults[opt])
|
|
||||||
|
|
||||||
|
|
||||||
def clear_config_states():
|
|
||||||
from charmhelpers.core import hookenv, unitdata
|
|
||||||
from charms.reactive import remove_state
|
|
||||||
config = hookenv.config()
|
|
||||||
remove_state('config.changed')
|
|
||||||
for opt in config.keys():
|
|
||||||
remove_state('config.changed.{}'.format(opt))
|
|
||||||
remove_state('config.set.{}'.format(opt))
|
|
||||||
remove_state('config.default.{}'.format(opt))
|
|
||||||
unitdata.kv().flush()
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
# Copyright 2014-2016 Canonical Limited.
|
|
||||||
#
|
|
||||||
# This file is part of layer-basic, the reactive base layer for Juju.
|
|
||||||
#
|
|
||||||
# charm-helpers is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
|
||||||
# published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# charm-helpers is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# This module may only import from the Python standard library.
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
'''
|
|
||||||
execd/preinstall
|
|
||||||
|
|
||||||
Read the layer-basic docs for more info on how to use this feature.
|
|
||||||
https://charmsreactive.readthedocs.io/en/latest/layer-basic.html#exec-d-support
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def default_execd_dir():
|
|
||||||
return os.path.join(os.environ['JUJU_CHARM_DIR'], 'exec.d')
|
|
||||||
|
|
||||||
|
|
||||||
def execd_module_paths(execd_dir=None):
|
|
||||||
"""Generate a list of full paths to modules within execd_dir."""
|
|
||||||
if not execd_dir:
|
|
||||||
execd_dir = default_execd_dir()
|
|
||||||
|
|
||||||
if not os.path.exists(execd_dir):
|
|
||||||
return
|
|
||||||
|
|
||||||
for subpath in os.listdir(execd_dir):
|
|
||||||
module = os.path.join(execd_dir, subpath)
|
|
||||||
if os.path.isdir(module):
|
|
||||||
yield module
|
|
||||||
|
|
||||||
|
|
||||||
def execd_submodule_paths(command, execd_dir=None):
|
|
||||||
"""Generate a list of full paths to the specified command within exec_dir.
|
|
||||||
"""
|
|
||||||
for module_path in execd_module_paths(execd_dir):
|
|
||||||
path = os.path.join(module_path, command)
|
|
||||||
if os.access(path, os.X_OK) and os.path.isfile(path):
|
|
||||||
yield path
|
|
||||||
|
|
||||||
|
|
||||||
def execd_sentinel_path(submodule_path):
|
|
||||||
module_path = os.path.dirname(submodule_path)
|
|
||||||
execd_path = os.path.dirname(module_path)
|
|
||||||
module_name = os.path.basename(module_path)
|
|
||||||
submodule_name = os.path.basename(submodule_path)
|
|
||||||
return os.path.join(execd_path,
|
|
||||||
'.{}_{}.done'.format(module_name, submodule_name))
|
|
||||||
|
|
||||||
|
|
||||||
def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
|
|
||||||
"""Run command for each module within execd_dir which defines it."""
|
|
||||||
if stderr is None:
|
|
||||||
stderr = sys.stdout
|
|
||||||
for submodule_path in execd_submodule_paths(command, execd_dir):
|
|
||||||
# Only run each execd once. We cannot simply run them in the
|
|
||||||
# install hook, as potentially storage hooks are run before that.
|
|
||||||
# We cannot rely on them being idempotent.
|
|
||||||
sentinel = execd_sentinel_path(submodule_path)
|
|
||||||
if os.path.exists(sentinel):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
subprocess.check_call([submodule_path], stderr=stderr,
|
|
||||||
universal_newlines=True)
|
|
||||||
with open(sentinel, 'w') as f:
|
|
||||||
f.write('{} ran successfully {}\n'.format(submodule_path,
|
|
||||||
time.ctime()))
|
|
||||||
f.write('Removing this file will cause it to be run again\n')
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
# Logs get the details. We can't use juju-log, as the
|
|
||||||
# output may be substantial and exceed command line
|
|
||||||
# length limits.
|
|
||||||
print("ERROR ({}) running {}".format(e.returncode, e.cmd),
|
|
||||||
file=stderr)
|
|
||||||
print("STDOUT<<EOM", file=stderr)
|
|
||||||
print(e.output, file=stderr)
|
|
||||||
print("EOM", file=stderr)
|
|
||||||
|
|
||||||
# Unit workload status gets a shorter fail message.
|
|
||||||
short_path = os.path.relpath(submodule_path)
|
|
||||||
block_msg = "Error ({}) running {}".format(e.returncode,
|
|
||||||
short_path)
|
|
||||||
try:
|
|
||||||
subprocess.check_call(['status-set', 'blocked', block_msg],
|
|
||||||
universal_newlines=True)
|
|
||||||
if stop_on_error:
|
|
||||||
sys.exit(0) # Leave unit in blocked state.
|
|
||||||
except Exception:
|
|
||||||
pass # We care about the exec.d/* failure, not status-set.
|
|
||||||
|
|
||||||
if stop_on_error:
|
|
||||||
sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
|
|
||||||
|
|
||||||
|
|
||||||
def execd_preinstall(execd_dir=None):
|
|
||||||
"""Run charm-pre-install for each module within execd_dir."""
|
|
||||||
execd_run('charm-pre-install', execd_dir=execd_dir)
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,26 +0,0 @@
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
_CHARM_PATH = Path(os.environ.get('JUJU_CHARM_DIR', '.'))
|
|
||||||
_DEFAULT_FILE = _CHARM_PATH / 'layer.yaml'
|
|
||||||
_CACHE = {}
|
|
||||||
|
|
||||||
|
|
||||||
def get(section=None, option=None, layer_file=_DEFAULT_FILE):
|
|
||||||
if option and not section:
|
|
||||||
raise ValueError('Cannot specify option without section')
|
|
||||||
|
|
||||||
layer_file = (_CHARM_PATH / layer_file).resolve()
|
|
||||||
if layer_file not in _CACHE:
|
|
||||||
with layer_file.open() as fp:
|
|
||||||
_CACHE[layer_file] = yaml.safe_load(fp.read())
|
|
||||||
|
|
||||||
data = _CACHE[layer_file].get('options', {})
|
|
||||||
if section:
|
|
||||||
data = data.get(section, {})
|
|
||||||
if option:
|
|
||||||
data = data.get(option)
|
|
||||||
return data
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
import inspect
|
|
||||||
import errno
|
|
||||||
import subprocess
|
|
||||||
import yaml
|
|
||||||
from enum import Enum
|
|
||||||
from functools import wraps
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
|
||||||
from charms import layer
|
|
||||||
|
|
||||||
|
|
||||||
_orig_call = subprocess.call
|
|
||||||
_statuses = {'_initialized': False,
|
|
||||||
'_finalized': False}
|
|
||||||
|
|
||||||
|
|
||||||
class WorkloadState(Enum):
|
|
||||||
"""
|
|
||||||
Enum of the valid workload states.
|
|
||||||
|
|
||||||
Valid options are:
|
|
||||||
|
|
||||||
* `WorkloadState.MAINTENANCE`
|
|
||||||
* `WorkloadState.BLOCKED`
|
|
||||||
* `WorkloadState.WAITING`
|
|
||||||
* `WorkloadState.ACTIVE`
|
|
||||||
"""
|
|
||||||
# note: order here determines precedence of state
|
|
||||||
MAINTENANCE = 'maintenance'
|
|
||||||
BLOCKED = 'blocked'
|
|
||||||
WAITING = 'waiting'
|
|
||||||
ACTIVE = 'active'
|
|
||||||
|
|
||||||
|
|
||||||
def maintenance(message):
|
|
||||||
"""
|
|
||||||
Set the status to the `MAINTENANCE` state with the given operator message.
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
status_set(WorkloadState.MAINTENANCE, message)
|
|
||||||
|
|
||||||
|
|
||||||
def maint(message):
|
|
||||||
"""
|
|
||||||
Shorthand alias for
|
|
||||||
[maintenance](status.md#charms.layer.status.maintenance).
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
maintenance(message)
|
|
||||||
|
|
||||||
|
|
||||||
def blocked(message):
|
|
||||||
"""
|
|
||||||
Set the status to the `BLOCKED` state with the given operator message.
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
status_set(WorkloadState.BLOCKED, message)
|
|
||||||
|
|
||||||
|
|
||||||
def waiting(message):
|
|
||||||
"""
|
|
||||||
Set the status to the `WAITING` state with the given operator message.
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
status_set(WorkloadState.WAITING, message)
|
|
||||||
|
|
||||||
|
|
||||||
def active(message):
|
|
||||||
"""
|
|
||||||
Set the status to the `ACTIVE` state with the given operator message.
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
status_set(WorkloadState.ACTIVE, message)
|
|
||||||
|
|
||||||
|
|
||||||
def status_set(workload_state, message):
|
|
||||||
"""
|
|
||||||
Set the status to the given workload state with a message.
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
`workload_state` (WorkloadState or str): State of the workload. Should be
|
|
||||||
a [WorkloadState](status.md#charms.layer.status.WorkloadState) enum
|
|
||||||
member, or the string value of one of those members.
|
|
||||||
`message` (str): Message to convey to the operator.
|
|
||||||
"""
|
|
||||||
if not isinstance(workload_state, WorkloadState):
|
|
||||||
workload_state = WorkloadState(workload_state)
|
|
||||||
if workload_state is WorkloadState.MAINTENANCE:
|
|
||||||
_status_set_immediate(workload_state, message)
|
|
||||||
return
|
|
||||||
layer = _find_calling_layer()
|
|
||||||
_statuses.setdefault(workload_state, []).append((layer, message))
|
|
||||||
if not _statuses['_initialized'] or _statuses['_finalized']:
|
|
||||||
# We either aren't initialized, so the finalizer may never be run,
|
|
||||||
# or the finalizer has already run, so it won't run again. In either
|
|
||||||
# case, we need to manually invoke it to ensure the status gets set.
|
|
||||||
_finalize()
|
|
||||||
|
|
||||||
|
|
||||||
def _find_calling_layer():
|
|
||||||
for frame in inspect.stack():
|
|
||||||
# switch to .filename when trusty (Python 3.4) is EOL
|
|
||||||
fn = Path(frame[1])
|
|
||||||
if fn.parent.stem not in ('reactive', 'layer', 'charms'):
|
|
||||||
continue
|
|
||||||
layer_name = fn.stem
|
|
||||||
if layer_name == 'status':
|
|
||||||
continue # skip our own frames
|
|
||||||
return layer_name
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _initialize():
|
|
||||||
if not _statuses['_initialized']:
|
|
||||||
if layer.options.get('status', 'patch-hookenv'):
|
|
||||||
_patch_hookenv()
|
|
||||||
hookenv.atexit(_finalize)
|
|
||||||
_statuses['_initialized'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def _finalize():
|
|
||||||
if _statuses['_initialized']:
|
|
||||||
# If we haven't been initialized, we can't truly be finalized.
|
|
||||||
# This makes things more efficient if an action sets a status
|
|
||||||
# but subsequently starts the reactive bus.
|
|
||||||
_statuses['_finalized'] = True
|
|
||||||
charm_name = hookenv.charm_name()
|
|
||||||
charm_dir = Path(hookenv.charm_dir())
|
|
||||||
with charm_dir.joinpath('layer.yaml').open() as fp:
|
|
||||||
includes = yaml.safe_load(fp.read()).get('includes', [])
|
|
||||||
layer_order = includes + [charm_name]
|
|
||||||
|
|
||||||
for workload_state in WorkloadState:
|
|
||||||
if workload_state not in _statuses:
|
|
||||||
continue
|
|
||||||
if not _statuses[workload_state]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _get_key(record):
|
|
||||||
layer_name, message = record
|
|
||||||
if layer_name in layer_order:
|
|
||||||
return layer_order.index(layer_name)
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
sorted_statuses = sorted(_statuses[workload_state], key=_get_key)
|
|
||||||
layer_name, message = sorted_statuses[-1]
|
|
||||||
_status_set_immediate(workload_state, message)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def _status_set_immediate(workload_state, message):
|
|
||||||
workload_state = workload_state.value
|
|
||||||
try:
|
|
||||||
hookenv.log('status-set: {}: {}'.format(workload_state, message),
|
|
||||||
hookenv.INFO)
|
|
||||||
ret = _orig_call(['status-set', workload_state, message])
|
|
||||||
if ret == 0:
|
|
||||||
return
|
|
||||||
except OSError as e:
|
|
||||||
# ignore status-set not available on older controllers
|
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_hookenv():
|
|
||||||
# we can't patch hookenv.status_set directly because other layers may have
|
|
||||||
# already imported it into their namespace, so we have to patch sp.call
|
|
||||||
subprocess.call = _patched_call
|
|
||||||
|
|
||||||
|
|
||||||
@wraps(_orig_call)
|
|
||||||
def _patched_call(cmd, *args, **kwargs):
|
|
||||||
if not isinstance(cmd, list) or cmd[0] != 'status-set':
|
|
||||||
return _orig_call(cmd, *args, **kwargs)
|
|
||||||
_, workload_state, message = cmd
|
|
||||||
status_set(workload_state, message)
|
|
||||||
return 0 # make hookenv.status_set not emit spurious failure logs
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
# Copyright 2015-2016 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# This file is part of the Leadership Layer for Juju.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License version 3, as
|
|
||||||
# published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
|
||||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE. See the GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
|
||||||
from charmhelpers.core import unitdata
|
|
||||||
|
|
||||||
from charms import reactive
|
|
||||||
from charms.reactive import not_unless
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['leader_get', 'leader_set']
|
|
||||||
|
|
||||||
|
|
||||||
@not_unless('leadership.is_leader')
|
|
||||||
def leader_set(*args, **kw):
|
|
||||||
'''Change leadership settings, per charmhelpers.core.hookenv.leader_set.
|
|
||||||
|
|
||||||
Settings may either be passed in as a single dictionary, or using
|
|
||||||
keyword arguments. All values must be strings.
|
|
||||||
|
|
||||||
The leadership.set.{key} reactive state will be set while the
|
|
||||||
leadership hook environment setting remains set.
|
|
||||||
|
|
||||||
Changed leadership settings will set the leadership.changed.{key}
|
|
||||||
and leadership.changed states. These states will remain set until
|
|
||||||
the following hook.
|
|
||||||
|
|
||||||
These state changes take effect immediately on the leader, and
|
|
||||||
in future hooks run on non-leaders. In this way both leaders and
|
|
||||||
non-leaders can share handlers, waiting on these states.
|
|
||||||
'''
|
|
||||||
if args:
|
|
||||||
if len(args) > 1:
|
|
||||||
raise TypeError('leader_set() takes 1 positional argument but '
|
|
||||||
'{} were given'.format(len(args)))
|
|
||||||
else:
|
|
||||||
settings = dict(args[0])
|
|
||||||
else:
|
|
||||||
settings = {}
|
|
||||||
settings.update(kw)
|
|
||||||
previous = unitdata.kv().getrange('leadership.settings.', strip=True)
|
|
||||||
|
|
||||||
for key, value in settings.items():
|
|
||||||
if value != previous.get(key):
|
|
||||||
reactive.set_state('leadership.changed.{}'.format(key))
|
|
||||||
reactive.set_state('leadership.changed')
|
|
||||||
reactive.helpers.toggle_state('leadership.set.{}'.format(key),
|
|
||||||
value is not None)
|
|
||||||
hookenv.leader_set(settings)
|
|
||||||
unitdata.kv().update(settings, prefix='leadership.settings.')
|
|
||||||
|
|
||||||
|
|
||||||
def leader_get(attribute=None):
|
|
||||||
'''Return leadership settings, per charmhelpers.core.hookenv.leader_get.'''
|
|
||||||
return hookenv.leader_get(attribute)
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
#!.tox/py3/bin/python
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from shutil import rmtree
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pydocmd.__main__
|
|
||||||
|
|
||||||
|
|
||||||
with patch('charmhelpers.core.hookenv.metadata') as metadata:
|
|
||||||
sys.path.insert(0, 'lib')
|
|
||||||
sys.path.insert(1, 'reactive')
|
|
||||||
print(sys.argv)
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
sys.argv.extend(['build'])
|
|
||||||
pydocmd.__main__.main()
|
|
||||||
rmtree('_build')
|
|
||||||
if os.path.exists('.unit-state.db'):
|
|
||||||
os.remove('.unit-state.db')
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
analysis:
|
|
||||||
attributes:
|
|
||||||
- name: language
|
|
||||||
result: python
|
|
||||||
- name: framework
|
|
||||||
result: reactive
|
|
||||||
bases:
|
|
||||||
- architectures:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
channel: '20.04'
|
|
||||||
name: ubuntu
|
|
||||||
- architectures:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
channel: '22.04'
|
|
||||||
name: ubuntu
|
|
||||||
- architectures:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
channel: '18.04'
|
|
||||||
name: ubuntu
|
|
||||||
charmcraft-started-at: '2022-07-14T00:00:00.000000Z'
|
|
||||||
charmcraft-version: 1.7.1
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
"name": "calico"
|
|
||||||
"summary": "A robust Software Defined Network from Project Calico"
|
|
||||||
"maintainers":
|
|
||||||
- "Tim Van Steenburgh <tim.van.steenburgh@canonical.com>"
|
|
||||||
- "George Kraft <george.kraft@canonical.com>"
|
|
||||||
- "Konstantinos Tsakalozos <kos.tsakalozos@canonical.com>"
|
|
||||||
- "Mike Wilson <mike.wilson@canonical.com>"
|
|
||||||
- "Kevin Monroe <kevin.monroe@canonical.com>"
|
|
||||||
- "Joe Borg <joseph.borg@canonical.com>"
|
|
||||||
"description": |
|
|
||||||
Deploys Calico as a background service and configures CNI for use with
|
|
||||||
calico on any principal charm that implements the kubernetes-cni interface.
|
|
||||||
"tags":
|
|
||||||
- "networking"
|
|
||||||
"series":
|
|
||||||
- "focal"
|
|
||||||
- "jammy"
|
|
||||||
- "bionic"
|
|
||||||
"requires":
|
|
||||||
"etcd":
|
|
||||||
"interface": "etcd"
|
|
||||||
"cni":
|
|
||||||
"interface": "kubernetes-cni"
|
|
||||||
"scope": "container"
|
|
||||||
"docs": "https://discourse.charmhub.io/t/calico-docs-index/6167"
|
|
||||||
"resources":
|
|
||||||
"calico":
|
|
||||||
"type": "file"
|
|
||||||
"filename": "calico.tar.gz"
|
|
||||||
"description": "Calico resource tarball for amd64"
|
|
||||||
"calico-arm64":
|
|
||||||
"type": "file"
|
|
||||||
"filename": "calico.tar.gz"
|
|
||||||
"description": "Calico resource tarball for arm64"
|
|
||||||
"calico-node-image":
|
|
||||||
"type": "file"
|
|
||||||
"filename": "calico-node-image.tar.gz"
|
|
||||||
"description": "calico-node container image"
|
|
||||||
"subordinate": !!bool "true"
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
site_name: 'Status Management Layer'
|
|
||||||
|
|
||||||
generate:
|
|
||||||
- status.md:
|
|
||||||
- charms.layer.status.WorkloadState
|
|
||||||
- charms.layer.status.maintenance
|
|
||||||
- charms.layer.status.maint
|
|
||||||
- charms.layer.status.blocked
|
|
||||||
- charms.layer.status.waiting
|
|
||||||
- charms.layer.status.active
|
|
||||||
- charms.layer.status.status_set
|
|
||||||
|
|
||||||
pages:
|
|
||||||
- Status Management Layer: status.md
|
|
||||||
|
|
||||||
gens_dir: docs
|
|
||||||
|
|
@ -1,768 +0,0 @@
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
import gzip
|
|
||||||
import traceback
|
|
||||||
import ipaddress
|
|
||||||
|
|
||||||
from conctl import getContainerRuntimeCtl
|
|
||||||
from socket import gethostname
|
|
||||||
from subprocess import check_call, check_output, CalledProcessError, STDOUT
|
|
||||||
|
|
||||||
from charms.leadership import leader_get, leader_set
|
|
||||||
from charms.reactive import when, when_not, when_any, set_state, remove_state
|
|
||||||
from charms.reactive import hook, is_state
|
|
||||||
from charms.reactive import endpoint_from_flag, endpoint_from_name
|
|
||||||
from charms.reactive import data_changed, any_file_changed
|
|
||||||
from charms.reactive import register_trigger
|
|
||||||
from charmhelpers.core.hookenv import (
|
|
||||||
log,
|
|
||||||
resource_get,
|
|
||||||
network_get,
|
|
||||||
unit_private_ip,
|
|
||||||
is_leader,
|
|
||||||
local_unit,
|
|
||||||
config as charm_config,
|
|
||||||
atexit,
|
|
||||||
env_proxy_settings
|
|
||||||
)
|
|
||||||
from charmhelpers.core.host import (
|
|
||||||
arch,
|
|
||||||
service,
|
|
||||||
service_restart,
|
|
||||||
service_running
|
|
||||||
)
|
|
||||||
from charmhelpers.core.templating import render
|
|
||||||
from charms.layer import kubernetes_common, status
|
|
||||||
from charms.layer.kubernetes_common import kubectl
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - Handle the 'stop' hook by stopping and uninstalling all the things.
|
|
||||||
|
|
||||||
os.environ['PATH'] += os.pathsep + os.path.join(os.sep, 'snap', 'bin')
|
|
||||||
|
|
||||||
try:
|
|
||||||
CTL = getContainerRuntimeCtl()
|
|
||||||
set_state('calico.ctl.ready')
|
|
||||||
except RuntimeError:
|
|
||||||
log(traceback.format_exc())
|
|
||||||
remove_state('calico.ctl.ready')
|
|
||||||
|
|
||||||
CALICOCTL_PATH = '/opt/calicoctl'
|
|
||||||
ETCD_KEY_PATH = os.path.join(CALICOCTL_PATH, 'etcd-key')
|
|
||||||
ETCD_CERT_PATH = os.path.join(CALICOCTL_PATH, 'etcd-cert')
|
|
||||||
ETCD_CA_PATH = os.path.join(CALICOCTL_PATH, 'etcd-ca')
|
|
||||||
|
|
||||||
register_trigger(
|
|
||||||
when="cni.kubeconfig.changed", clear_flag="calico.service.installed"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hook('upgrade-charm')
|
|
||||||
def upgrade_charm():
|
|
||||||
remove_state('calico.binaries.installed')
|
|
||||||
remove_state('calico.cni.configured')
|
|
||||||
remove_state('calico.service.installed')
|
|
||||||
remove_state('calico.pool.configured')
|
|
||||||
remove_state('calico.npc.deployed')
|
|
||||||
remove_state('calico.image.pulled')
|
|
||||||
remove_state('calico.bgp.globals.configured')
|
|
||||||
remove_state('calico.node.configured')
|
|
||||||
remove_state('calico.bgp.peers.configured')
|
|
||||||
try:
|
|
||||||
log('Deleting /etc/cni/net.d/10-calico.conf')
|
|
||||||
os.remove('/etc/cni/net.d/10-calico.conf')
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
log(e)
|
|
||||||
if is_leader():
|
|
||||||
leader_set({
|
|
||||||
'calico-v3-data-migration-needed': None,
|
|
||||||
'calico-v3-npc-cleanup-needed': None,
|
|
||||||
'calico-v3-completion-needed': None,
|
|
||||||
'calico-v3-data-ready': None
|
|
||||||
})
|
|
||||||
cni = endpoint_from_name('cni')
|
|
||||||
cni.manage_flags()
|
|
||||||
|
|
||||||
|
|
||||||
@when_not('calico.binaries.installed')
|
|
||||||
def install_calico_binaries():
|
|
||||||
''' Unpack the Calico binaries. '''
|
|
||||||
# on intel, the resource is called 'calico'; other arches have a suffix
|
|
||||||
architecture = arch()
|
|
||||||
if architecture == "amd64":
|
|
||||||
resource_name = 'calico'
|
|
||||||
else:
|
|
||||||
resource_name = 'calico-{}'.format(architecture)
|
|
||||||
|
|
||||||
try:
|
|
||||||
archive = resource_get(resource_name)
|
|
||||||
except Exception:
|
|
||||||
message = 'Error fetching the calico resource.'
|
|
||||||
log(message)
|
|
||||||
status.blocked(message)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not archive:
|
|
||||||
message = 'Missing calico resource.'
|
|
||||||
log(message)
|
|
||||||
status.blocked(message)
|
|
||||||
return
|
|
||||||
|
|
||||||
filesize = os.stat(archive).st_size
|
|
||||||
if filesize < 1000000:
|
|
||||||
message = 'Incomplete calico resource'
|
|
||||||
log(message)
|
|
||||||
status.blocked(message)
|
|
||||||
return
|
|
||||||
|
|
||||||
status.maintenance('Unpacking calico resource.')
|
|
||||||
|
|
||||||
charm_dir = os.getenv('CHARM_DIR')
|
|
||||||
unpack_path = os.path.join(charm_dir, 'files', 'calico')
|
|
||||||
os.makedirs(unpack_path, exist_ok=True)
|
|
||||||
cmd = ['tar', 'xfz', archive, '-C', unpack_path]
|
|
||||||
log(cmd)
|
|
||||||
check_call(cmd)
|
|
||||||
|
|
||||||
apps = [
|
|
||||||
{'name': 'calicoctl', 'path': CALICOCTL_PATH},
|
|
||||||
{'name': 'calico', 'path': '/opt/cni/bin'},
|
|
||||||
{'name': 'calico-ipam', 'path': '/opt/cni/bin'},
|
|
||||||
]
|
|
||||||
|
|
||||||
for app in apps:
|
|
||||||
unpacked = os.path.join(unpack_path, app['name'])
|
|
||||||
app_path = os.path.join(app['path'], app['name'])
|
|
||||||
install = ['install', '-v', '-D', unpacked, app_path]
|
|
||||||
check_call(install)
|
|
||||||
|
|
||||||
calicoctl_path = '/usr/local/bin/calicoctl'
|
|
||||||
render('calicoctl', calicoctl_path, {})
|
|
||||||
os.chmod(calicoctl_path, 0o775)
|
|
||||||
|
|
||||||
set_state('calico.binaries.installed')
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available')
|
|
||||||
def update_calicoctl_env():
|
|
||||||
env = get_calicoctl_env()
|
|
||||||
lines = ['export %s=%s' % item for item in sorted(env.items())]
|
|
||||||
output = '\n'.join(lines)
|
|
||||||
with open('/opt/calicoctl/calicoctl.env', 'w') as f:
|
|
||||||
f.write(output)
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed')
|
|
||||||
@when_not('etcd.connected')
|
|
||||||
def blocked_without_etcd():
|
|
||||||
status.blocked('Waiting for relation to etcd')
|
|
||||||
|
|
||||||
|
|
||||||
@when('etcd.tls.available')
|
|
||||||
@when_not('calico.etcd-credentials.installed')
|
|
||||||
def install_etcd_credentials():
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
etcd.save_client_credentials(ETCD_KEY_PATH, ETCD_CERT_PATH, ETCD_CA_PATH)
|
|
||||||
# register initial etcd data so that we can detect changes
|
|
||||||
data_changed('calico.etcd.data', (etcd.get_connection_string(),
|
|
||||||
etcd.get_client_credentials()))
|
|
||||||
set_state('calico.etcd-credentials.installed')
|
|
||||||
|
|
||||||
|
|
||||||
@when('etcd.tls.available', 'calico.service.installed')
|
|
||||||
def check_etcd_changes():
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
if data_changed('calico.etcd.data', (etcd.get_connection_string(),
|
|
||||||
etcd.get_client_credentials())):
|
|
||||||
etcd.save_client_credentials(ETCD_KEY_PATH,
|
|
||||||
ETCD_CERT_PATH,
|
|
||||||
ETCD_CA_PATH)
|
|
||||||
remove_state('calico.service.installed')
|
|
||||||
remove_state('calico.npc.deployed')
|
|
||||||
remove_state('calico.cni.configured')
|
|
||||||
|
|
||||||
|
|
||||||
def get_mtu():
|
|
||||||
''' Get user-specified MTU size, adjusted to make room for encapsulation
|
|
||||||
headers. https://docs.projectcalico.org/networking/mtu
|
|
||||||
'''
|
|
||||||
mtu = charm_config('veth-mtu')
|
|
||||||
if not mtu:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if charm_config('vxlan') != 'Never':
|
|
||||||
return mtu - 50
|
|
||||||
elif charm_config('ipip') != 'Never':
|
|
||||||
return mtu - 20
|
|
||||||
return mtu
|
|
||||||
|
|
||||||
|
|
||||||
def get_bind_address():
|
|
||||||
''' Returns a non-fan bind address for the cni endpoint '''
|
|
||||||
try:
|
|
||||||
data = network_get('cni')
|
|
||||||
except NotImplementedError:
|
|
||||||
# Juju < 2.1
|
|
||||||
return unit_private_ip()
|
|
||||||
|
|
||||||
if 'bind-addresses' not in data:
|
|
||||||
# Juju < 2.3
|
|
||||||
return unit_private_ip()
|
|
||||||
|
|
||||||
for bind_address in data['bind-addresses']:
|
|
||||||
if bind_address['interfacename'].startswith('fan-'):
|
|
||||||
continue
|
|
||||||
return bind_address['addresses'][0]['address']
|
|
||||||
|
|
||||||
# If we made it here, we didn't find a non-fan CNI bind-address, which is
|
|
||||||
# unexpected. Let's log a message and play it safe.
|
|
||||||
log('Could not find a non-fan bind-address. Using private-address.')
|
|
||||||
return unit_private_ip()
|
|
||||||
|
|
||||||
|
|
||||||
@when('leadership.is_leader')
|
|
||||||
@when_not('leadership.set.calico-node-token')
|
|
||||||
def create_calico_node_token():
|
|
||||||
''' Create the system:calico-node user token '''
|
|
||||||
status.maintenance('Creating system:calico-node user token')
|
|
||||||
token = kubernetes_common.token_generator()
|
|
||||||
user = 'system:calico-node'
|
|
||||||
success = kubernetes_common.create_secret(
|
|
||||||
token=token,
|
|
||||||
username=user,
|
|
||||||
user=user
|
|
||||||
)
|
|
||||||
if not success:
|
|
||||||
log('Failed to create system:calico-node user token, will retry')
|
|
||||||
status.waiting('Waiting to retry creating calico-node token')
|
|
||||||
return
|
|
||||||
# create_secret may have added the <user>:: prefix. Get the new token.
|
|
||||||
token = kubernetes_common.get_secret_password(user)
|
|
||||||
if not token:
|
|
||||||
log('Failed to get system:calico-node user token, will retry')
|
|
||||||
status.waiting('Waiting to retry creating calico-node token')
|
|
||||||
return
|
|
||||||
leader_set({'calico-node-token': token})
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available',
|
|
||||||
'calico.etcd-credentials.installed', 'cni.kubeconfig.available',
|
|
||||||
'leadership.set.calico-node-token')
|
|
||||||
@when_not('calico.service.installed')
|
|
||||||
def install_calico_service():
|
|
||||||
''' Install the calico-node systemd service. '''
|
|
||||||
status.maintenance('Installing calico-node service.')
|
|
||||||
|
|
||||||
with open(kubernetes_common.kubeclientconfig_path) as f:
|
|
||||||
kubeconfig = yaml.safe_load(f)
|
|
||||||
any_file_changed([kubernetes_common.kubeclientconfig_path])
|
|
||||||
kubeconfig['users'] = [{
|
|
||||||
'name': 'calico-node',
|
|
||||||
'user': {
|
|
||||||
'token': leader_get('calico-node-token')
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
kubeconfig['contexts'][0]['context']['user'] = 'calico-node'
|
|
||||||
with open('/opt/calicoctl/kubeconfig', 'w') as f:
|
|
||||||
yaml.dump(kubeconfig, f)
|
|
||||||
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
service_path = os.path.join(os.sep, 'lib', 'systemd', 'system',
|
|
||||||
'calico-node.service')
|
|
||||||
ip_versions = {net.version for net in get_networks(charm_config('cidr'))}
|
|
||||||
ip4 = get_bind_address() if 4 in ip_versions else "none"
|
|
||||||
ip6 = "autodetect" if 6 in ip_versions else "none"
|
|
||||||
render('calico-node.service', service_path, {
|
|
||||||
'connection_string': etcd.get_connection_string(),
|
|
||||||
'etcd_key_path': ETCD_KEY_PATH,
|
|
||||||
'etcd_ca_path': ETCD_CA_PATH,
|
|
||||||
'etcd_cert_path': ETCD_CERT_PATH,
|
|
||||||
'nodename': gethostname(),
|
|
||||||
# specify IP so calico doesn't grab a silly one from, say, lxdbr0
|
|
||||||
'ip': ip4,
|
|
||||||
'ip6': ip6,
|
|
||||||
'mtu': get_mtu(),
|
|
||||||
'calico_node_image': charm_config('calico-node-image'),
|
|
||||||
'ignore_loose_rpf': charm_config('ignore-loose-rpf'),
|
|
||||||
'lc_all': os.environ.get('LC_ALL', 'C.UTF-8'),
|
|
||||||
'lang': os.environ.get('LANG', 'C.UTF-8')
|
|
||||||
})
|
|
||||||
check_call(['systemctl', 'daemon-reload'])
|
|
||||||
service_restart('calico-node')
|
|
||||||
service('enable', 'calico-node')
|
|
||||||
remove_state('cni.kubeconfig.changed')
|
|
||||||
set_state('calico.service.installed')
|
|
||||||
|
|
||||||
|
|
||||||
@when('config.changed.veth-mtu')
|
|
||||||
def configure_mtu():
|
|
||||||
remove_state('calico.service.installed')
|
|
||||||
remove_state('calico.cni.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when('config.changed.ignore-loose-rpf')
|
|
||||||
def ignore_loose_rpf_changed():
|
|
||||||
remove_state('calico.service.installed')
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available',
|
|
||||||
'calico.etcd-credentials.installed')
|
|
||||||
@when_not('calico.pool.configured')
|
|
||||||
def configure_calico_pool():
|
|
||||||
''' Configure Calico IP pool. '''
|
|
||||||
config = charm_config()
|
|
||||||
if not config['manage-pools']:
|
|
||||||
log('Skipping pool configuration')
|
|
||||||
set_state('calico.pool.configured')
|
|
||||||
return
|
|
||||||
|
|
||||||
status.maintenance('Configuring Calico IP pool')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# remove unrecognized pools, and default pool if CIDR doesn't match
|
|
||||||
pools = calicoctl_get('pool')['items']
|
|
||||||
|
|
||||||
cidrs = tuple(cidr.strip() for cidr in config['cidr'].split(','))
|
|
||||||
names = tuple('ipv{}'.format(get_network(cidr).version)
|
|
||||||
for cidr in cidrs)
|
|
||||||
pool_names_to_delete = [
|
|
||||||
pool['metadata']['name'] for pool in pools
|
|
||||||
if pool['metadata']['name'] not in names
|
|
||||||
or pool['spec']['cidr'] not in cidrs
|
|
||||||
]
|
|
||||||
|
|
||||||
for pool_name in pool_names_to_delete:
|
|
||||||
log('Deleting pool: %s' % pool_name)
|
|
||||||
calicoctl('delete', 'pool', pool_name, '--skip-not-exists')
|
|
||||||
|
|
||||||
for cidr, name in zip(cidrs, names):
|
|
||||||
# configure the default pool
|
|
||||||
pool = {
|
|
||||||
'apiVersion': 'projectcalico.org/v3',
|
|
||||||
'kind': 'IPPool',
|
|
||||||
'metadata': {
|
|
||||||
'name': name,
|
|
||||||
},
|
|
||||||
'spec': {
|
|
||||||
'cidr': cidr,
|
|
||||||
'ipipMode': config['ipip'],
|
|
||||||
'vxlanMode': config['vxlan'],
|
|
||||||
'natOutgoing': config['nat-outgoing'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
calicoctl_apply(pool)
|
|
||||||
except CalledProcessError:
|
|
||||||
log(traceback.format_exc())
|
|
||||||
if config['ipip'] != 'Never' and config['vxlan'] != 'Never':
|
|
||||||
status.blocked('ipip and vxlan configs are in conflict')
|
|
||||||
else:
|
|
||||||
status.waiting('Waiting to retry calico pool configuration')
|
|
||||||
return
|
|
||||||
|
|
||||||
set_state('calico.pool.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.ipip', 'config.changed.nat-outgoing',
|
|
||||||
'config.changed.cidr', 'config.changed.manage-pools',
|
|
||||||
'config.changed.vxlan')
|
|
||||||
def reconfigure_calico_pool():
|
|
||||||
''' Reconfigure the Calico IP pool '''
|
|
||||||
remove_state('calico.pool.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when('etcd.available', 'cni.connected')
|
|
||||||
@when_not('calico.cni.configured')
|
|
||||||
def configure_cni():
|
|
||||||
''' Configure Calico CNI. '''
|
|
||||||
status.maintenance('Configuring Calico CNI')
|
|
||||||
cni = endpoint_from_flag('cni.connected')
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
os.makedirs('/etc/cni/net.d', exist_ok=True)
|
|
||||||
ip_versions = {net.version for net in get_networks(charm_config('cidr'))}
|
|
||||||
context = {
|
|
||||||
'connection_string': etcd.get_connection_string(),
|
|
||||||
'etcd_key_path': ETCD_KEY_PATH,
|
|
||||||
'etcd_cert_path': ETCD_CERT_PATH,
|
|
||||||
'etcd_ca_path': ETCD_CA_PATH,
|
|
||||||
'kubeconfig_path': '/opt/calicoctl/kubeconfig',
|
|
||||||
'mtu': get_mtu(),
|
|
||||||
'assign_ipv4': 'true' if 4 in ip_versions else 'false',
|
|
||||||
'assign_ipv6': 'true' if 6 in ip_versions else 'false',
|
|
||||||
}
|
|
||||||
render('10-calico.conflist', '/etc/cni/net.d/10-calico.conflist', context)
|
|
||||||
config = charm_config()
|
|
||||||
cni.set_config(cidr=config['cidr'], cni_conf_file='10-calico.conflist')
|
|
||||||
set_state('calico.cni.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.cidr')
|
|
||||||
def reconfigure_cni():
|
|
||||||
remove_state('calico.cni.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when('etcd.available', 'calico.cni.configured',
|
|
||||||
'calico.service.installed', 'leadership.is_leader')
|
|
||||||
@when_not('calico.npc.deployed')
|
|
||||||
def deploy_network_policy_controller():
|
|
||||||
''' Deploy the Calico network policy controller. '''
|
|
||||||
status.maintenance('Deploying network policy controller.')
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
context = {
|
|
||||||
'connection_string': etcd.get_connection_string(),
|
|
||||||
'etcd_key_path': ETCD_KEY_PATH,
|
|
||||||
'etcd_cert_path': ETCD_CERT_PATH,
|
|
||||||
'etcd_ca_path': ETCD_CA_PATH,
|
|
||||||
'calico_policy_image': charm_config('calico-policy-image'),
|
|
||||||
'etcd_cert_last_modified': os.path.getmtime(ETCD_CERT_PATH)
|
|
||||||
}
|
|
||||||
render('policy-controller.yaml', '/tmp/policy-controller.yaml', context)
|
|
||||||
try:
|
|
||||||
kubectl('apply', '-f', '/tmp/policy-controller.yaml')
|
|
||||||
set_state('calico.npc.deployed')
|
|
||||||
except CalledProcessError as e:
|
|
||||||
status.waiting('Waiting for kubernetes')
|
|
||||||
log(str(e))
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available')
|
|
||||||
@when_not('calico.bgp.globals.configured')
|
|
||||||
def configure_bgp_globals():
|
|
||||||
status.maintenance('Configuring BGP globals')
|
|
||||||
config = charm_config()
|
|
||||||
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
bgp_config = calicoctl_get('bgpconfig', 'default')
|
|
||||||
except CalledProcessError as e:
|
|
||||||
if b'resource does not exist' in e.output:
|
|
||||||
log('default BGPConfiguration does not exist')
|
|
||||||
bgp_config = {
|
|
||||||
'apiVersion': 'projectcalico.org/v3',
|
|
||||||
'kind': 'BGPConfiguration',
|
|
||||||
'metadata': {
|
|
||||||
'name': 'default'
|
|
||||||
},
|
|
||||||
'spec': {}
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
spec = bgp_config['spec']
|
|
||||||
spec['asNumber'] = config['global-as-number']
|
|
||||||
spec['nodeToNodeMeshEnabled'] = config['node-to-node-mesh']
|
|
||||||
spec['serviceClusterIPs'] = [
|
|
||||||
{'cidr': cidr}
|
|
||||||
for cidr in config['bgp-service-cluster-ips'].split()
|
|
||||||
]
|
|
||||||
spec['serviceExternalIPs'] = [
|
|
||||||
{'cidr': cidr}
|
|
||||||
for cidr in config['bgp-service-external-ips'].split()
|
|
||||||
]
|
|
||||||
spec['serviceLoadBalancerIPs'] = [
|
|
||||||
{'cidr': cidr}
|
|
||||||
for cidr in config['bgp-service-loadbalancer-ips'].split()
|
|
||||||
]
|
|
||||||
calicoctl_apply(bgp_config)
|
|
||||||
except CalledProcessError:
|
|
||||||
log(traceback.format_exc())
|
|
||||||
status.waiting('Waiting to retry BGP global configuration')
|
|
||||||
return
|
|
||||||
|
|
||||||
set_state('calico.bgp.globals.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.global-as-number',
|
|
||||||
'config.changed.node-to-node-mesh',
|
|
||||||
'config.changed.bgp-service-cluster-ips',
|
|
||||||
'config.changed.bgp-service-external-ips',
|
|
||||||
'config.changed.bgp-service-loadbalancer-ips')
|
|
||||||
def reconfigure_bgp_globals():
|
|
||||||
remove_state('calico.bgp.globals.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available')
|
|
||||||
@when_not('calico.node.configured')
|
|
||||||
def configure_node():
|
|
||||||
status.maintenance('Configuring Calico node')
|
|
||||||
|
|
||||||
node_name = gethostname()
|
|
||||||
as_number = get_unit_as_number()
|
|
||||||
route_reflector_cluster_id = get_route_reflector_cluster_id()
|
|
||||||
|
|
||||||
try:
|
|
||||||
node = calicoctl_get('node', node_name)
|
|
||||||
node['spec']['bgp']['asNumber'] = as_number
|
|
||||||
node['spec']['bgp']['routeReflectorClusterID'] = \
|
|
||||||
route_reflector_cluster_id
|
|
||||||
calicoctl_apply(node)
|
|
||||||
except CalledProcessError:
|
|
||||||
log(traceback.format_exc())
|
|
||||||
status.waiting('Waiting to retry Calico node configuration')
|
|
||||||
return
|
|
||||||
|
|
||||||
set_state('calico.node.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.subnet-as-numbers', 'config.changed.unit-as-numbers',
|
|
||||||
'config.changed.route-reflector-cluster-ids')
|
|
||||||
def reconfigure_node():
|
|
||||||
remove_state('calico.node.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.binaries.installed', 'etcd.available')
|
|
||||||
@when_not('calico.bgp.peers.configured')
|
|
||||||
def configure_bgp_peers():
|
|
||||||
status.maintenance('Configuring BGP peers')
|
|
||||||
|
|
||||||
peers = []
|
|
||||||
|
|
||||||
# Global BGP peers
|
|
||||||
config = charm_config()
|
|
||||||
peers += yaml.safe_load(config['global-bgp-peers'])
|
|
||||||
|
|
||||||
# Subnet-scoped BGP peers
|
|
||||||
subnet_bgp_peers = yaml.safe_load(config['subnet-bgp-peers'])
|
|
||||||
subnets = filter_local_subnets(subnet_bgp_peers)
|
|
||||||
for subnet in subnets:
|
|
||||||
peers += subnet_bgp_peers[str(subnet)]
|
|
||||||
|
|
||||||
# Unit-scoped BGP peers
|
|
||||||
unit_id = get_unit_id()
|
|
||||||
unit_bgp_peers = yaml.safe_load(config['unit-bgp-peers'])
|
|
||||||
if unit_id in unit_bgp_peers:
|
|
||||||
peers += unit_bgp_peers[unit_id]
|
|
||||||
|
|
||||||
# Give names to peers
|
|
||||||
safe_unit_name = local_unit().replace('/', '-')
|
|
||||||
named_peers = {
|
|
||||||
# name must consist of lower case alphanumeric characters, '-' or '.'
|
|
||||||
'%s-%s-%s' % (safe_unit_name, peer['address'].replace(':', '-'),
|
|
||||||
peer['as-number']): peer
|
|
||||||
for peer in peers
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
node_name = gethostname()
|
|
||||||
for peer_name, peer in named_peers.items():
|
|
||||||
peer_def = {
|
|
||||||
'apiVersion': 'projectcalico.org/v3',
|
|
||||||
'kind': 'BGPPeer',
|
|
||||||
'metadata': {
|
|
||||||
'name': peer_name,
|
|
||||||
},
|
|
||||||
'spec': {
|
|
||||||
'node': node_name,
|
|
||||||
'peerIP': peer['address'],
|
|
||||||
'asNumber': peer['as-number']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
calicoctl_apply(peer_def)
|
|
||||||
|
|
||||||
# Delete unrecognized peers
|
|
||||||
existing_peers = calicoctl_get('bgppeers')['items']
|
|
||||||
existing_peers = [peer['metadata']['name'] for peer in existing_peers]
|
|
||||||
peers_to_delete = [
|
|
||||||
peer for peer in existing_peers
|
|
||||||
if peer.startswith(safe_unit_name + '-')
|
|
||||||
and peer not in named_peers
|
|
||||||
]
|
|
||||||
|
|
||||||
for peer in peers_to_delete:
|
|
||||||
calicoctl('delete', 'bgppeer', peer)
|
|
||||||
except CalledProcessError:
|
|
||||||
log(traceback.format_exc())
|
|
||||||
status.waiting('Waiting to retry BGP peer configuration')
|
|
||||||
return
|
|
||||||
|
|
||||||
set_state('calico.bgp.peers.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.global-bgp-peers', 'config.changed.subnet-bgp-peers',
|
|
||||||
'config.changed.unit-bgp-peers')
|
|
||||||
def reconfigure_bgp_peers():
|
|
||||||
remove_state('calico.bgp.peers.configured')
|
|
||||||
|
|
||||||
|
|
||||||
@atexit
|
|
||||||
def ready():
|
|
||||||
preconditions = [
|
|
||||||
'calico.service.installed', 'calico.pool.configured',
|
|
||||||
'calico.cni.configured', 'calico.bgp.globals.configured',
|
|
||||||
'calico.node.configured', 'calico.bgp.peers.configured'
|
|
||||||
]
|
|
||||||
if is_rpf_config_mismatched():
|
|
||||||
status.blocked('ignore-loose-rpf config is in conflict with rp_filter value')
|
|
||||||
return
|
|
||||||
if is_state('upgrade.series.in-progress'):
|
|
||||||
status.blocked('Series upgrade in progress')
|
|
||||||
return
|
|
||||||
for precondition in preconditions:
|
|
||||||
if not is_state(precondition):
|
|
||||||
return
|
|
||||||
if is_leader() and not is_state('calico.npc.deployed'):
|
|
||||||
status.waiting('Waiting to retry deploying policy controller')
|
|
||||||
return
|
|
||||||
if not service_running('calico-node'):
|
|
||||||
status.waiting('Waiting for service: calico-node')
|
|
||||||
return
|
|
||||||
status.active('Calico is active')
|
|
||||||
|
|
||||||
|
|
||||||
def calicoctl(*args):
|
|
||||||
cmd = ['/opt/calicoctl/calicoctl'] + list(args)
|
|
||||||
env = os.environ.copy()
|
|
||||||
env.update(get_calicoctl_env())
|
|
||||||
try:
|
|
||||||
return check_output(cmd, env=env, stderr=STDOUT)
|
|
||||||
except CalledProcessError as e:
|
|
||||||
log(e.output)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def set_http_proxy():
|
|
||||||
"""
|
|
||||||
Check if we have any values for
|
|
||||||
juju_http*_proxy and apply them.
|
|
||||||
"""
|
|
||||||
juju_environment = env_proxy_settings()
|
|
||||||
if juju_environment and not juju_environment.get('disable-juju-proxy'):
|
|
||||||
upper = ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']
|
|
||||||
lower = list(map(str.lower, upper))
|
|
||||||
keys = upper + lower
|
|
||||||
for key in keys:
|
|
||||||
from_juju = juju_environment.get(key, None)
|
|
||||||
if from_juju:
|
|
||||||
os.environ[key] = from_juju
|
|
||||||
|
|
||||||
|
|
||||||
@when_not('calico.image.pulled')
|
|
||||||
@when('calico.ctl.ready')
|
|
||||||
def pull_calico_node_image():
|
|
||||||
image = resource_get('calico-node-image')
|
|
||||||
|
|
||||||
if not image or os.path.getsize(image) == 0:
|
|
||||||
status.maintenance('Pulling calico-node image')
|
|
||||||
image = charm_config('calico-node-image')
|
|
||||||
set_http_proxy()
|
|
||||||
CTL.pull(image)
|
|
||||||
else:
|
|
||||||
status.maintenance('Loading calico-node image')
|
|
||||||
unzipped = '/tmp/calico-node-image.tar'
|
|
||||||
with gzip.open(image, 'rb') as f_in:
|
|
||||||
with open(unzipped, 'wb') as f_out:
|
|
||||||
f_out.write(f_in.read())
|
|
||||||
CTL.load(unzipped)
|
|
||||||
|
|
||||||
set_state('calico.image.pulled')
|
|
||||||
|
|
||||||
|
|
||||||
@when_any('config.changed.calico-node-image')
|
|
||||||
def repull_calico_node_image():
|
|
||||||
remove_state('calico.image.pulled')
|
|
||||||
remove_state('calico.service.installed')
|
|
||||||
|
|
||||||
|
|
||||||
@when('calico.service.installed', 'calico.pool.configured')
|
|
||||||
def disable_vxlan_tx_checksumming():
|
|
||||||
'''Workaround for https://github.com/projectcalico/calico/issues/3145'''
|
|
||||||
config = charm_config()
|
|
||||||
|
|
||||||
if config['disable-vxlan-tx-checksumming'] and config['vxlan'] != 'Never':
|
|
||||||
cmd = ['ethtool', '-K', 'vxlan.calico', 'tx-checksum-ip-generic',
|
|
||||||
'off']
|
|
||||||
try:
|
|
||||||
check_call(cmd)
|
|
||||||
except CalledProcessError:
|
|
||||||
msg = 'Waiting to retry disabling VXLAN TX checksumming'
|
|
||||||
log(msg)
|
|
||||||
status.waiting(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def calicoctl_get(*args):
|
|
||||||
args = ['get', '-o', 'yaml', '--export'] + list(args)
|
|
||||||
output = calicoctl(*args)
|
|
||||||
result = yaml.safe_load(output)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def calicoctl_apply(data):
|
|
||||||
path = '/tmp/calicoctl-apply.yaml'
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
yaml.dump(data, f)
|
|
||||||
calicoctl('apply', '-f', path)
|
|
||||||
|
|
||||||
|
|
||||||
def get_calicoctl_env():
|
|
||||||
etcd = endpoint_from_flag('etcd.available')
|
|
||||||
env = {}
|
|
||||||
env['ETCD_ENDPOINTS'] = etcd.get_connection_string()
|
|
||||||
env['ETCD_KEY_FILE'] = ETCD_KEY_PATH
|
|
||||||
env['ETCD_CERT_FILE'] = ETCD_CERT_PATH
|
|
||||||
env['ETCD_CA_CERT_FILE'] = ETCD_CA_PATH
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def get_unit_as_number():
|
|
||||||
config = charm_config()
|
|
||||||
|
|
||||||
# Check for matching unit rule
|
|
||||||
unit_id = get_unit_id()
|
|
||||||
unit_as_numbers = yaml.safe_load(config['unit-as-numbers'])
|
|
||||||
if unit_id in unit_as_numbers:
|
|
||||||
as_number = unit_as_numbers[unit_id]
|
|
||||||
return as_number
|
|
||||||
|
|
||||||
# Check for matching subnet rule
|
|
||||||
subnet_as_numbers = yaml.safe_load(config['subnet-as-numbers'])
|
|
||||||
subnets = filter_local_subnets(subnet_as_numbers)
|
|
||||||
if subnets:
|
|
||||||
subnets.sort(key=lambda subnet: -subnet.prefixlen)
|
|
||||||
subnet = subnets[0]
|
|
||||||
as_number = subnet_as_numbers[str(subnet)]
|
|
||||||
return as_number
|
|
||||||
|
|
||||||
# No AS number specified for this unit.
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def filter_local_subnets(subnets):
|
|
||||||
ip_address = get_bind_address()
|
|
||||||
ip_address = ipaddress.ip_address(ip_address) # IP address
|
|
||||||
subnets = [ipaddress.ip_network(subnet) for subnet in subnets]
|
|
||||||
subnets = [subnet for subnet in subnets if ip_address in subnet]
|
|
||||||
return subnets
|
|
||||||
|
|
||||||
|
|
||||||
def get_unit_id():
|
|
||||||
return int(local_unit().split('/')[1])
|
|
||||||
|
|
||||||
|
|
||||||
def get_route_reflector_cluster_id():
|
|
||||||
config = charm_config()
|
|
||||||
route_reflector_cluster_ids = yaml.safe_load(
|
|
||||||
config['route-reflector-cluster-ids']
|
|
||||||
)
|
|
||||||
unit_id = get_unit_id()
|
|
||||||
return route_reflector_cluster_ids.get(unit_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_network(cidr):
|
|
||||||
'''Convert a CIDR to a network instance.'''
|
|
||||||
return ipaddress.ip_interface(cidr.strip()).network
|
|
||||||
|
|
||||||
|
|
||||||
def get_networks(cidrs):
|
|
||||||
'''Convert a comma-separated list of CIDRs to a list of networks.'''
|
|
||||||
return [get_network(cidr) for cidr in cidrs.split(',')]
|
|
||||||
|
|
||||||
|
|
||||||
def is_rpf_config_mismatched():
|
|
||||||
with open('/proc/sys/net/ipv4/conf/all/rp_filter') as f:
|
|
||||||
rp_filter = int(f.read())
|
|
||||||
ignore_loose_rpf = charm_config('ignore-loose-rpf')
|
|
||||||
if rp_filter == 2 and not ignore_loose_rpf:
|
|
||||||
# calico says this is invalid
|
|
||||||
# https://github.com/kubernetes-sigs/kind/issues/891
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
# Copyright 2015-2016 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# This file is part of the Leadership Layer for Juju.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License version 3, as
|
|
||||||
# published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
|
||||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
|
||||||
# PURPOSE. See the GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
|
||||||
from charmhelpers.core import unitdata
|
|
||||||
|
|
||||||
from charms import reactive
|
|
||||||
from charms.leadership import leader_get, leader_set
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['leader_get', 'leader_set'] # Backwards compatibility
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_leadership_state():
|
|
||||||
'''Initialize leadership.* states from the hook environment.
|
|
||||||
|
|
||||||
Invoked by hookenv.atstart() so states are available in
|
|
||||||
@hook decorated handlers.
|
|
||||||
'''
|
|
||||||
is_leader = hookenv.is_leader()
|
|
||||||
if is_leader:
|
|
||||||
hookenv.log('Initializing Leadership Layer (is leader)')
|
|
||||||
else:
|
|
||||||
hookenv.log('Initializing Leadership Layer (is follower)')
|
|
||||||
|
|
||||||
reactive.helpers.toggle_state('leadership.is_leader', is_leader)
|
|
||||||
|
|
||||||
previous = unitdata.kv().getrange('leadership.settings.', strip=True)
|
|
||||||
current = hookenv.leader_get()
|
|
||||||
|
|
||||||
# Handle deletions.
|
|
||||||
for key in set(previous.keys()) - set(current.keys()):
|
|
||||||
current[key] = None
|
|
||||||
|
|
||||||
any_changed = False
|
|
||||||
for key, value in current.items():
|
|
||||||
reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
|
|
||||||
value != previous.get(key))
|
|
||||||
if value != previous.get(key):
|
|
||||||
any_changed = True
|
|
||||||
reactive.helpers.toggle_state('leadership.set.{}'.format(key),
|
|
||||||
value is not None)
|
|
||||||
reactive.helpers.toggle_state('leadership.changed', any_changed)
|
|
||||||
|
|
||||||
unitdata.kv().update(current, prefix='leadership.settings.')
|
|
||||||
|
|
||||||
|
|
||||||
# Per https://github.com/juju-solutions/charms.reactive/issues/33,
|
|
||||||
# this module may be imported multiple times so ensure the
|
|
||||||
# initialization hook is only registered once. I have to piggy back
|
|
||||||
# onto the namespace of a module imported before reactive discovery
|
|
||||||
# to do this.
|
|
||||||
if not hasattr(reactive, '_leadership_registered'):
|
|
||||||
hookenv.atstart(initialize_leadership_state)
|
|
||||||
reactive._leadership_registered = True
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
from charms import layer
|
|
||||||
|
|
||||||
|
|
||||||
layer.status._initialize()
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
mock
|
|
||||||
flake8
|
|
||||||
pytest
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
0
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"name": "calico-k8s-network",
|
|
||||||
"cniVersion": "0.3.1",
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"type": "calico",
|
|
||||||
"etcd_endpoints": "{{ connection_string }}",
|
|
||||||
"etcd_key_file": "{{ etcd_key_path }}",
|
|
||||||
"etcd_cert_file": "{{ etcd_cert_path }}",
|
|
||||||
"etcd_ca_cert_file": "{{ etcd_ca_path }}",
|
|
||||||
"log_level": "info",
|
|
||||||
{% if mtu -%}
|
|
||||||
"mtu": {{ mtu }},
|
|
||||||
{%- endif %}
|
|
||||||
"ipam": {
|
|
||||||
"type": "calico-ipam",
|
|
||||||
"assign_ipv4": "{{ assign_ipv4 }}",
|
|
||||||
"assign_ipv6": "{{ assign_ipv6 }}"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"type": "k8s"
|
|
||||||
},
|
|
||||||
"kubernetes": {
|
|
||||||
"kubeconfig": "{{ kubeconfig_path }}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "portmap",
|
|
||||||
"capabilities": {"portMappings": true},
|
|
||||||
"snat": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=calico node
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=root
|
|
||||||
Environment=ETCD_ENDPOINTS={{ connection_string }}
|
|
||||||
# Setting LC_ALL and LANG works around a bug that only occurs on Xenial
|
|
||||||
# https://bugs.launchpad.net/bugs/1911220
|
|
||||||
Environment=LC_ALL={{ lc_all }}
|
|
||||||
Environment=LANG={{ lang }}
|
|
||||||
PermissionsStartOnly=true
|
|
||||||
ExecStartPre=-/usr/local/sbin/charm-env --charm calico conctl delete calico-node
|
|
||||||
ExecStartPre=/bin/mkdir -p /var/run/calico /var/log/calico /var/lib/calico
|
|
||||||
ExecStart=/usr/local/sbin/charm-env --charm calico conctl run \
|
|
||||||
--rm \
|
|
||||||
--net-host \
|
|
||||||
--privileged \
|
|
||||||
--env DATASTORE_TYPE=etcdv3 \
|
|
||||||
--env ETCD_ENDPOINTS={{ connection_string }} \
|
|
||||||
--env ETCD_CA_CERT_FILE={{ etcd_ca_path }} \
|
|
||||||
--env ETCD_CERT_FILE={{ etcd_cert_path }} \
|
|
||||||
--env ETCD_KEY_FILE={{ etcd_key_path }} \
|
|
||||||
--env NODENAME={{ nodename }} \
|
|
||||||
--env CALICO_K8S_NODE_REF={{ nodename }} \
|
|
||||||
--env IP={{ ip }} \
|
|
||||||
--env KUBECONFIG=/opt/calicoctl/kubeconfig \
|
|
||||||
{% if ipv4 == "none" -%}
|
|
||||||
--env CALICO_ROUTER_ID="hash" \
|
|
||||||
{% endif -%}
|
|
||||||
--env IP6={{ ip6 }} \
|
|
||||||
{% if ip6 != "none" -%}
|
|
||||||
--env FELIX_IPV6SUPPORT=true \
|
|
||||||
{% endif -%}
|
|
||||||
--env NO_DEFAULT_POOLS=true \
|
|
||||||
--env AS= \
|
|
||||||
--env CALICO_LIBNETWORK_ENABLED=true \
|
|
||||||
--env CALICO_NETWORKING_BACKEND=bird \
|
|
||||||
--env FELIX_DEFAULTENDPOINTTOHOSTACTION=ACCEPT \
|
|
||||||
--env FELIX_IGNORELOOSERPF={{ ignore_loose_rpf | string | lower }} \
|
|
||||||
{% if mtu -%}
|
|
||||||
--env FELIX_IPINIPMTU={{ mtu }} \
|
|
||||||
--env FELIX_VXLANMTU={{ mtu }} \
|
|
||||||
{% endif -%}
|
|
||||||
--env CALICO_MANAGE_CNI=false \
|
|
||||||
--mount /lib/modules:/lib/modules \
|
|
||||||
--mount /var/run/calico:/var/run/calico \
|
|
||||||
--mount /var/log/calico:/var/log/calico \
|
|
||||||
--mount /var/lib/calico:/var/lib/calico \
|
|
||||||
--mount /opt/calicoctl:/opt/calicoctl \
|
|
||||||
--name calico-node \
|
|
||||||
{{ calico_node_image }}
|
|
||||||
ExecStop=-/usr/local/sbin/charm-env --charm calico conctl delete calico-node
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
. /opt/calicoctl/calicoctl.env
|
|
||||||
exec /opt/calicoctl/calicoctl "$@"
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Manifest for CK secrets that auth-webhook expects
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: {{ secret_name }}
|
|
||||||
namespace: {{ secret_namespace }}
|
|
||||||
type: {{ type }}
|
|
||||||
data:
|
|
||||||
uid: {{ user }}
|
|
||||||
username: {{ username }}
|
|
||||||
password: {{ password }}
|
|
||||||
groups: '{{ groups }}'
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
# Calico manifest for Charmed Kubernetes.
|
|
||||||
#
|
|
||||||
# Pulled from upstream on 2022-01-24 at
|
|
||||||
# https://docs.projectcalico.org/archive/v3.21/manifests/calico-etcd.yaml
|
|
||||||
#
|
|
||||||
# Search "CK edit" to find all the changes that were made for this charm.
|
|
||||||
---
|
|
||||||
# CK edit: Remove calico-etcd-secrets secret
|
|
||||||
---
|
|
||||||
# CK edit: Remove calico-config ConfigMap
|
|
||||||
---
|
|
||||||
# Source: calico/templates/calico-kube-controllers-rbac.yaml
|
|
||||||
|
|
||||||
# Include a clusterrole for the kube-controllers component,
|
|
||||||
# and bind it to the calico-kube-controllers serviceaccount.
|
|
||||||
kind: ClusterRole
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
rules:
|
|
||||||
# Pods are monitored for changing labels.
|
|
||||||
# The node controller monitors Kubernetes nodes.
|
|
||||||
# Namespace and serviceaccount labels are used for policy.
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources:
|
|
||||||
- pods
|
|
||||||
- nodes
|
|
||||||
- namespaces
|
|
||||||
- serviceaccounts
|
|
||||||
verbs:
|
|
||||||
- watch
|
|
||||||
- list
|
|
||||||
- get
|
|
||||||
# Watch for changes to Kubernetes NetworkPolicies.
|
|
||||||
- apiGroups: ["networking.k8s.io"]
|
|
||||||
resources:
|
|
||||||
- networkpolicies
|
|
||||||
verbs:
|
|
||||||
- watch
|
|
||||||
- list
|
|
||||||
---
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: calico-kube-controllers
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: calico-kube-controllers
|
|
||||||
namespace: kube-system
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
# Source: calico/templates/calico-node-rbac.yaml
|
|
||||||
# Include a clusterrole for the calico-node DaemonSet,
|
|
||||||
# and bind it to the calico-node serviceaccount.
|
|
||||||
kind: ClusterRole
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: calico-node
|
|
||||||
rules:
|
|
||||||
# The CNI plugin needs to get pods, nodes, and namespaces.
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources:
|
|
||||||
- pods
|
|
||||||
- nodes
|
|
||||||
- namespaces
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
# EndpointSlices are used for Service-based network policy rule
|
|
||||||
# enforcement.
|
|
||||||
- apiGroups: ["discovery.k8s.io"]
|
|
||||||
resources:
|
|
||||||
- endpointslices
|
|
||||||
verbs:
|
|
||||||
- watch
|
|
||||||
- list
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources:
|
|
||||||
- endpoints
|
|
||||||
- services
|
|
||||||
verbs:
|
|
||||||
# Used to discover service IPs for advertisement.
|
|
||||||
- watch
|
|
||||||
- list
|
|
||||||
# Pod CIDR auto-detection on kubeadm needs access to config maps.
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources:
|
|
||||||
- configmaps
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources:
|
|
||||||
- nodes/status
|
|
||||||
verbs:
|
|
||||||
# Needed for clearing NodeNetworkUnavailable flag.
|
|
||||||
- patch
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: calico-node
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: calico-node
|
|
||||||
subjects:
|
|
||||||
# CK edit: Bind to the system:calico-node user, not the calico-node ServiceAccount.
|
|
||||||
- kind: User
|
|
||||||
name: system:calico-node
|
|
||||||
|
|
||||||
---
|
|
||||||
# CK edit: Remove the calico-node DaemonSet
|
|
||||||
---
|
|
||||||
# CK edit: Remove the calico-node ServiceAccount
|
|
||||||
---
|
|
||||||
# Source: calico/templates/calico-kube-controllers.yaml
|
|
||||||
# See https://github.com/projectcalico/kube-controllers
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
namespace: kube-system
|
|
||||||
labels:
|
|
||||||
k8s-app: calico-kube-controllers
|
|
||||||
# CK edit: Add cdk-restart-on-ca-change label
|
|
||||||
cdk-restart-on-ca-change: "true"
|
|
||||||
spec:
|
|
||||||
# The controllers can only have a single active instance.
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
k8s-app: calico-kube-controllers
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
namespace: kube-system
|
|
||||||
labels:
|
|
||||||
k8s-app: calico-kube-controllers
|
|
||||||
# CK edit: Add cdk-etcd-cert-last-modified annotation
|
|
||||||
annotations:
|
|
||||||
# annotate etcd cert modification time, so that when it changes, k8s
|
|
||||||
# will restart the pod
|
|
||||||
cdk-etcd-cert-last-modified: "{{ etcd_cert_last_modified }}"
|
|
||||||
spec:
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/os: linux
|
|
||||||
tolerations:
|
|
||||||
# Mark the pod as a critical add-on for rescheduling.
|
|
||||||
- key: CriticalAddonsOnly
|
|
||||||
operator: Exists
|
|
||||||
# wokeignore:rule=master - CK edit: pass woke check
|
|
||||||
- key: node-role.kubernetes.io/master
|
|
||||||
effect: NoSchedule
|
|
||||||
serviceAccountName: calico-kube-controllers
|
|
||||||
priorityClassName: system-cluster-critical
|
|
||||||
# The controllers must run in the host network namespace so that
|
|
||||||
# it isn't governed by policy that would prevent it from working.
|
|
||||||
hostNetwork: true
|
|
||||||
containers:
|
|
||||||
- name: calico-kube-controllers
|
|
||||||
# CK edit: Use image from calico_policy_image template variable
|
|
||||||
image: {{ calico_policy_image }}
|
|
||||||
env:
|
|
||||||
# CK edit: Use etcd connection details from template variables
|
|
||||||
- name: ETCD_ENDPOINTS
|
|
||||||
value: {{ connection_string }}
|
|
||||||
- name: ETCD_CA_CERT_FILE
|
|
||||||
value: {{ etcd_ca_path }}
|
|
||||||
- name: ETCD_CERT_FILE
|
|
||||||
value: {{ etcd_cert_path }}
|
|
||||||
- name: ETCD_KEY_FILE
|
|
||||||
value: {{ etcd_key_path }}
|
|
||||||
# Choose which controllers to run.
|
|
||||||
- name: ENABLED_CONTROLLERS
|
|
||||||
value: policy,namespace,serviceaccount,workloadendpoint,node
|
|
||||||
volumeMounts:
|
|
||||||
# CK edit: Mount calicoctl volume
|
|
||||||
- name: calicoctl
|
|
||||||
mountPath: /opt/calicoctl
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- /usr/bin/check-status
|
|
||||||
- -l
|
|
||||||
periodSeconds: 10
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
failureThreshold: 6
|
|
||||||
timeoutSeconds: 10
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- /usr/bin/check-status
|
|
||||||
- -r
|
|
||||||
periodSeconds: 10
|
|
||||||
volumes:
|
|
||||||
# CK edit: Mount calicoctl volume
|
|
||||||
- name: calicoctl
|
|
||||||
hostPath:
|
|
||||||
path: /opt/calicoctl
|
|
||||||
---
|
|
||||||
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
namespace: kube-system
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# This manifest creates a Pod Disruption Budget for Controller to allow K8s Cluster Autoscaler to evict
|
|
||||||
|
|
||||||
apiVersion: policy/v1beta1
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
metadata:
|
|
||||||
name: calico-kube-controllers
|
|
||||||
namespace: kube-system
|
|
||||||
labels:
|
|
||||||
k8s-app: calico-kube-controllers
|
|
||||||
spec:
|
|
||||||
maxUnavailable: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
k8s-app: calico-kube-controllers
|
|
||||||
|
|
||||||
---
|
|
||||||
# Source: calico/templates/calico-typha.yaml
|
|
||||||
|
|
||||||
---
|
|
||||||
# Source: calico/templates/configure-canal.yaml
|
|
||||||
|
|
||||||
---
|
|
||||||
# Source: calico/templates/kdd-crds.yaml
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
type: charm
|
|
||||||
bases:
|
|
||||||
- name: ubuntu
|
|
||||||
channel: "20.04"
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
options:
|
|
||||||
as-number:
|
|
||||||
type: int
|
|
||||||
description: AS Number
|
|
||||||
default: 64512
|
|
||||||
bgp-peers:
|
|
||||||
type: string
|
|
||||||
description: BGP peers
|
|
||||||
default: "[]"
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
name: bird
|
|
||||||
description: |
|
|
||||||
Test charm running BIRD
|
|
||||||
summary: |
|
|
||||||
Test charm running BIRD
|
|
||||||
series:
|
|
||||||
- focal
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
ops
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from ops.charm import CharmBase
|
|
||||||
from ops.main import main
|
|
||||||
from ops.model import ActiveStatus, MaintenanceStatus
|
|
||||||
from subprocess import check_call
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
bird_config_base = """
|
|
||||||
log syslog all;
|
|
||||||
debug protocols all;
|
|
||||||
|
|
||||||
protocol kernel {
|
|
||||||
persist;
|
|
||||||
scan time 20;
|
|
||||||
export all;
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol device {
|
|
||||||
scan time 10;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
bird_config_peer = """
|
|
||||||
protocol bgp {
|
|
||||||
import all;
|
|
||||||
local as %s;
|
|
||||||
neighbor %s as %s;
|
|
||||||
direct;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class BirdCharm(CharmBase):
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
self.framework.observe(self.on.install, self.install)
|
|
||||||
self.framework.observe(self.on.config_changed, self.config_changed)
|
|
||||||
|
|
||||||
def install(self, event):
|
|
||||||
self.unit.status = MaintenanceStatus("Installing BIRD")
|
|
||||||
check_call(['apt-get', 'update'])
|
|
||||||
check_call(['apt-get', 'install', '-y', 'bird'])
|
|
||||||
|
|
||||||
def config_changed(self, event):
|
|
||||||
self.unit.status = MaintenanceStatus("Configuring BIRD")
|
|
||||||
as_number = self.config['as-number']
|
|
||||||
bird_config = "\n".join([bird_config_base] + [
|
|
||||||
bird_config_peer % (as_number, peer['address'], peer['as-number'])
|
|
||||||
for peer in yaml.safe_load(self.config['bgp-peers'])
|
|
||||||
])
|
|
||||||
with open('/etc/bird/bird.conf', 'w') as f:
|
|
||||||
f.write(bird_config)
|
|
||||||
check_call(['systemctl', 'reload', 'bird'])
|
|
||||||
self.unit.status = ActiveStatus()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(BirdCharm)
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
description: A minimal two-machine Kubernetes cluster, appropriate for development.
|
|
||||||
series: focal
|
|
||||||
machines:
|
|
||||||
'0':
|
|
||||||
constraints: cores=2 mem=4G root-disk=16G
|
|
||||||
series: focal
|
|
||||||
'1':
|
|
||||||
constraints: cores=4 mem=4G root-disk=16G
|
|
||||||
series: focal
|
|
||||||
services:
|
|
||||||
containerd:
|
|
||||||
charm: containerd
|
|
||||||
channel: edge
|
|
||||||
easyrsa:
|
|
||||||
charm: easyrsa
|
|
||||||
channel: edge
|
|
||||||
num_units: 1
|
|
||||||
to:
|
|
||||||
- '1'
|
|
||||||
etcd:
|
|
||||||
charm: etcd
|
|
||||||
channel: edge
|
|
||||||
num_units: 1
|
|
||||||
options:
|
|
||||||
channel: 3.4/stable
|
|
||||||
to:
|
|
||||||
- '0'
|
|
||||||
calico:
|
|
||||||
charm: {{calico_charm}}
|
|
||||||
resources:
|
|
||||||
calico: {{resource_path}}/calico-amd64.tar.gz
|
|
||||||
calico-arm64: {{resource_path}}/calico-arm64.tar.gz
|
|
||||||
calico-node-image: {{resource_path}}/calico-node-image.tar.gz
|
|
||||||
options:
|
|
||||||
ignore-loose-rpf: true
|
|
||||||
vxlan: Always
|
|
||||||
kubernetes-control-plane:
|
|
||||||
charm: kubernetes-control-plane
|
|
||||||
channel: latest/edge
|
|
||||||
constraints: cores=2 mem=4G root-disk=16G
|
|
||||||
expose: true
|
|
||||||
num_units: 1
|
|
||||||
options:
|
|
||||||
channel: 1.23/edge
|
|
||||||
to:
|
|
||||||
- '0'
|
|
||||||
kubernetes-worker:
|
|
||||||
charm: kubernetes-worker
|
|
||||||
channel: edge
|
|
||||||
constraints: cores=4 mem=4G root-disk=16G
|
|
||||||
expose: true
|
|
||||||
num_units: 1
|
|
||||||
options:
|
|
||||||
channel: 1.23/edge
|
|
||||||
to:
|
|
||||||
- '1'
|
|
||||||
relations:
|
|
||||||
- - kubernetes-control-plane:kube-control
|
|
||||||
- kubernetes-worker:kube-control
|
|
||||||
- - kubernetes-control-plane:certificates
|
|
||||||
- easyrsa:client
|
|
||||||
- - kubernetes-control-plane:etcd
|
|
||||||
- etcd:db
|
|
||||||
- - kubernetes-worker:certificates
|
|
||||||
- easyrsa:client
|
|
||||||
- - etcd:certificates
|
|
||||||
- easyrsa:client
|
|
||||||
- - calico:etcd
|
|
||||||
- etcd:db
|
|
||||||
- - calico:cni
|
|
||||||
- kubernetes-control-plane:cni
|
|
||||||
- - calico:cni
|
|
||||||
- kubernetes-worker:cni
|
|
||||||
- - containerd:containerd
|
|
||||||
- kubernetes-worker:container-runtime
|
|
||||||
- - containerd:containerd
|
|
||||||
- kubernetes-control-plane:container-runtime
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"ifname": "ens192",
|
|
||||||
"operstate": "UP",
|
|
||||||
"addr_info": [
|
|
||||||
{
|
|
||||||
"local": "10.246.154.77",
|
|
||||||
"prefixlen": 24,
|
|
||||||
"metric": 100
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ifname": "lxdbr0",
|
|
||||||
"operstate": "UP",
|
|
||||||
"addr_info": [
|
|
||||||
{
|
|
||||||
"local": "10.111.246.1",
|
|
||||||
"prefixlen": 24
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link_index": 4,
|
|
||||||
"ifname": "veth890e3a36",
|
|
||||||
"operstate": "UP",
|
|
||||||
"addr_info": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import charms.unit_test
|
|
||||||
|
|
||||||
|
|
||||||
charms.unit_test.patch_reactive()
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest import mock
|
|
||||||
from charms.layer import kubernetes_common
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateKubeConfig:
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _files(self, tmp_path):
|
|
||||||
self.cfg_file = tmp_path / "config"
|
|
||||||
self.ca_file = tmp_path / "ca.crt"
|
|
||||||
self.ca_file.write_text("foo")
|
|
||||||
self.ckc = partial(
|
|
||||||
kubernetes_common.create_kubeconfig,
|
|
||||||
self.cfg_file,
|
|
||||||
"server",
|
|
||||||
self.ca_file,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_guard_clauses(self):
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
self.ckc()
|
|
||||||
assert not self.cfg_file.exists()
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
self.ckc(token="token", password="password")
|
|
||||||
assert not self.cfg_file.exists()
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
self.ckc(key="key")
|
|
||||||
assert not self.cfg_file.exists()
|
|
||||||
|
|
||||||
def test_file_creation(self):
|
|
||||||
self.ckc(password="password")
|
|
||||||
assert self.cfg_file.exists()
|
|
||||||
cfg_data_1 = self.cfg_file.read_text()
|
|
||||||
assert cfg_data_1
|
|
||||||
|
|
||||||
def test_idempotency(self):
|
|
||||||
self.ckc(password="password")
|
|
||||||
cfg_data_1 = self.cfg_file.read_text()
|
|
||||||
self.ckc(password="password")
|
|
||||||
cfg_data_2 = self.cfg_file.read_text()
|
|
||||||
# Verify that calling w/ the same data keeps the same file contents.
|
|
||||||
assert cfg_data_2 == cfg_data_1
|
|
||||||
|
|
||||||
def test_efficient_updates(self):
|
|
||||||
self.ckc(password="old_password")
|
|
||||||
cfg_stat_1 = self.cfg_file.stat()
|
|
||||||
self.ckc(password="old_password")
|
|
||||||
cfg_stat_2 = self.cfg_file.stat()
|
|
||||||
self.ckc(password="new_password")
|
|
||||||
cfg_stat_3 = self.cfg_file.stat()
|
|
||||||
# Verify that calling with the same data doesn't
|
|
||||||
# modify the file at all, but that new data does
|
|
||||||
assert cfg_stat_1.st_mtime == cfg_stat_2.st_mtime < cfg_stat_3.st_mtime
|
|
||||||
|
|
||||||
def test_aws_iam(self):
|
|
||||||
self.ckc(password="password", aws_iam_cluster_id="aws-cluster")
|
|
||||||
assert self.cfg_file.exists()
|
|
||||||
cfg_data_1 = self.cfg_file.read_text()
|
|
||||||
assert "aws-cluster" in cfg_data_1
|
|
||||||
|
|
||||||
def test_keystone(self):
|
|
||||||
self.ckc(password="password", keystone=True)
|
|
||||||
assert self.cfg_file.exists()
|
|
||||||
cfg_data_1 = self.cfg_file.read_text()
|
|
||||||
assert "keystone-user" in cfg_data_1
|
|
||||||
assert "exec" in cfg_data_1
|
|
||||||
|
|
||||||
def test_atomic_updates(self):
|
|
||||||
self.ckc(password="old_password")
|
|
||||||
with self.cfg_file.open("rt") as f:
|
|
||||||
# Perform a write in the middle of reading
|
|
||||||
self.ckc(password="new_password")
|
|
||||||
# Read data from existing FH after new data was written
|
|
||||||
cfg_data_1 = f.read()
|
|
||||||
# Read updated data
|
|
||||||
cfg_data_2 = self.cfg_file.read_text()
|
|
||||||
# Verify that the in-progress read didn't get any of the new data
|
|
||||||
assert cfg_data_1 != cfg_data_2
|
|
||||||
assert "old_password" in cfg_data_1
|
|
||||||
assert "new_password" in cfg_data_2
|
|
||||||
|
|
||||||
@mock.patch("charmhelpers.core.hookenv.network_get", autospec=True)
|
|
||||||
def test_get_ingress_address(self, network_get):
|
|
||||||
network_get.return_value = {"ingress-addresses": ["1.2.3.4", "5.6.7.8"]}
|
|
||||||
ingress = kubernetes_common.get_ingress_address("endpoint-name")
|
|
||||||
assert ingress == "1.2.3.4"
|
|
||||||
ingress = kubernetes_common.get_ingress_address("endpoint-name", ["1.2.3.4"])
|
|
||||||
assert ingress == "5.6.7.8"
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
from kubernetes_wrapper import Kubernetes
|
|
||||||
import logging
|
|
||||||
import pytest
|
|
||||||
import pytest_asyncio
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="module")
|
|
||||||
async def kubernetes(ops_test):
|
|
||||||
kubeconfig_path = ops_test.tmp_path / "kubeconfig"
|
|
||||||
retcode, stdout, stderr = await ops_test.run(
|
|
||||||
"juju", "scp", "kubernetes-control-plane/leader:config", kubeconfig_path
|
|
||||||
)
|
|
||||||
if retcode != 0:
|
|
||||||
log.error(f"retcode: {retcode}")
|
|
||||||
log.error(f"stdout:\n{stdout.strip()}")
|
|
||||||
log.error(f"stderr:\n{stderr.strip()}")
|
|
||||||
pytest.fail("Failed to copy kubeconfig from kubernetes-control-plane")
|
|
||||||
namespace = "test-calico-integration-" + "".join(
|
|
||||||
random.choice(string.ascii_lowercase + string.digits)
|
|
||||||
for _ in range(5)
|
|
||||||
)
|
|
||||||
kubernetes = Kubernetes(namespace, kubeconfig=str(kubeconfig_path))
|
|
||||||
namespace_object = {
|
|
||||||
'apiVersion': 'v1',
|
|
||||||
'kind': 'Namespace',
|
|
||||||
'metadata': {
|
|
||||||
'name': namespace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kubernetes.apply_object(namespace_object)
|
|
||||||
yield kubernetes
|
|
||||||
kubernetes.delete_object(namespace_object)
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pytest
|
|
||||||
import pytest_asyncio
|
|
||||||
import time
|
|
||||||
import yaml
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="module")
|
|
||||||
async def build_all_charms(ops_test):
|
|
||||||
charms = await asyncio.gather(
|
|
||||||
ops_test.build_charm("."),
|
|
||||||
ops_test.build_charm("tests/data/bird-operator")
|
|
||||||
)
|
|
||||||
yield charms
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
|
||||||
async def calico_charm(build_all_charms):
|
|
||||||
yield build_all_charms[0]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
|
||||||
async def bird_charm(build_all_charms):
|
|
||||||
yield build_all_charms[1]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.abort_on_fail
|
|
||||||
@pytest.mark.skip_if_deployed
|
|
||||||
async def test_build_and_deploy(ops_test, calico_charm):
|
|
||||||
resource_path = ops_test.tmp_path / "charm-resources"
|
|
||||||
resource_path.mkdir()
|
|
||||||
resource_build_script = os.path.abspath("./build-calico-resource.sh")
|
|
||||||
log.info("Building charm resources")
|
|
||||||
retcode, stdout, stderr = await ops_test.run(
|
|
||||||
resource_build_script,
|
|
||||||
cwd=resource_path
|
|
||||||
)
|
|
||||||
if retcode != 0:
|
|
||||||
log.error(f"retcode: {retcode}")
|
|
||||||
log.error(f"stdout:\n{stdout.strip()}")
|
|
||||||
log.error(f"stderr:\n{stderr.strip()}")
|
|
||||||
pytest.fail("Failed to build charm resources")
|
|
||||||
bundle = ops_test.render_bundle(
|
|
||||||
"tests/data/bundle.yaml",
|
|
||||||
calico_charm=calico_charm,
|
|
||||||
resource_path=resource_path
|
|
||||||
)
|
|
||||||
# deploy with Juju CLI because libjuju does not support local resource
|
|
||||||
# paths in bundles
|
|
||||||
log.info("Deploying bundle")
|
|
||||||
retcode, stdout, stderr = await ops_test.run(
|
|
||||||
"juju", "deploy", "-m", ops_test.model_full_name, bundle
|
|
||||||
)
|
|
||||||
if retcode != 0:
|
|
||||||
log.error(f"retcode: {retcode}")
|
|
||||||
log.error(f"stdout:\n{stdout.strip()}")
|
|
||||||
log.error(f"stderr:\n{stderr.strip()}")
|
|
||||||
pytest.fail("Failed to deploy bundle")
|
|
||||||
|
|
||||||
try:
|
|
||||||
await ops_test.model.wait_for_idle(wait_for_active=True, timeout=60 * 60)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
k8s_cp = "kubernetes-control-plane"
|
|
||||||
assert k8s_cp in ops_test.model.applications
|
|
||||||
app = ops_test.model.applications[k8s_cp]
|
|
||||||
assert app.units, f"No {k8s_cp} units available"
|
|
||||||
unit = app.units[0]
|
|
||||||
if "kube-system pod" in unit.workload_status_message:
|
|
||||||
log.debug(
|
|
||||||
await juju_run(
|
|
||||||
unit, "kubectl --kubeconfig /root/.kube/config get all -A"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
async def juju_run(unit, cmd):
|
|
||||||
result = await unit.run(cmd)
|
|
||||||
code = result.results["Code"]
|
|
||||||
stdout = result.results.get("Stdout")
|
|
||||||
stderr = result.results.get("Stderr")
|
|
||||||
assert code == "0", f"{cmd} failed ({code}): {stderr or stdout}"
|
|
||||||
return stdout
|
|
||||||
|
|
||||||
|
|
||||||
async def test_bgp_service_ip_advertisement(ops_test, bird_charm, kubernetes):
|
|
||||||
# deploy a test service in k8s (nginx)
|
|
||||||
deployment = {
|
|
||||||
'apiVersion': 'apps/v1',
|
|
||||||
'kind': 'Deployment',
|
|
||||||
'metadata': {
|
|
||||||
'name': 'nginx'
|
|
||||||
},
|
|
||||||
'spec': {
|
|
||||||
'selector': {
|
|
||||||
'matchLabels': {
|
|
||||||
'app': 'nginx'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'template': {
|
|
||||||
'metadata': {
|
|
||||||
'labels': {
|
|
||||||
'app': 'nginx'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'spec': {
|
|
||||||
'containers': [{
|
|
||||||
'name': 'nginx',
|
|
||||||
'image': 'rocks.canonical.com/cdk/nginx:1.18',
|
|
||||||
'ports': [{
|
|
||||||
'containerPort': 80
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
service = {
|
|
||||||
'apiVersion': 'v1',
|
|
||||||
'kind': 'Service',
|
|
||||||
'metadata': {
|
|
||||||
'name': 'nginx'
|
|
||||||
},
|
|
||||||
'spec': {
|
|
||||||
'selector': {
|
|
||||||
'app': 'nginx'
|
|
||||||
},
|
|
||||||
'ports': [{
|
|
||||||
'protocol': 'TCP',
|
|
||||||
'port': 80
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
kubernetes.apply_object(deployment)
|
|
||||||
kubernetes.apply_object(service)
|
|
||||||
service_ip = kubernetes.read_object(service).spec.cluster_ip
|
|
||||||
|
|
||||||
# deploy bird charm
|
|
||||||
await ops_test.model.deploy(bird_charm)
|
|
||||||
await ops_test.model.wait_for_idle(wait_for_active=True, timeout=60 * 10)
|
|
||||||
|
|
||||||
# configure calico to peer with bird
|
|
||||||
k8s_cp = "kubernetes-control-plane"
|
|
||||||
k8s_cp_config = await ops_test.model.applications[k8s_cp].get_config()
|
|
||||||
bird_app = ops_test.model.applications['bird']
|
|
||||||
calico_app = ops_test.model.applications['calico']
|
|
||||||
await calico_app.set_config({
|
|
||||||
'bgp-service-cluster-ips': k8s_cp_config['service-cidr']['value'],
|
|
||||||
'global-bgp-peers': yaml.dump([
|
|
||||||
{'address': unit.public_address, 'as-number': 64512}
|
|
||||||
for unit in bird_app.units
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
# configure bird to peer with calico
|
|
||||||
await bird_app.set_config({
|
|
||||||
'bgp-peers': yaml.dump([
|
|
||||||
{'address': unit.public_address, 'as-number': 64512}
|
|
||||||
for unit in calico_app.units
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
# verify test service is reachable from bird
|
|
||||||
deadline = time.time() + 60 * 10
|
|
||||||
while time.time() < deadline:
|
|
||||||
retcode, stdout, stderr = await ops_test.run(
|
|
||||||
'juju', 'ssh', '-m', ops_test.model_full_name, 'bird/leader',
|
|
||||||
'curl', '--connect-timeout', '10', service_ip
|
|
||||||
)
|
|
||||||
if retcode == 0:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
pytest.fail("Failed service connection test after BGP config")
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
await calico_app.set_config({
|
|
||||||
'bgp-service-cluster-ips': '',
|
|
||||||
'global-bgp-peers': '[]'
|
|
||||||
})
|
|
||||||
await bird_app.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_rp_filter_conflict(ops_test):
|
|
||||||
unit_number = 0
|
|
||||||
await ops_test.juju(
|
|
||||||
'ssh', f'calico/{unit_number}', 'sudo sysctl -w net.ipv4.conf.all.rp_filter=2',
|
|
||||||
check=True,
|
|
||||||
fail_msg="Failed to set rp_filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
calico_app = ops_test.model.applications['calico']
|
|
||||||
# false is default, change it to true and back to false to trigger config changed
|
|
||||||
await calico_app.set_config({
|
|
||||||
'ignore-loose-rpf': "true",
|
|
||||||
})
|
|
||||||
await calico_app.set_config({
|
|
||||||
'ignore-loose-rpf': "false",
|
|
||||||
})
|
|
||||||
|
|
||||||
unit = calico_app.units[unit_number]
|
|
||||||
|
|
||||||
def blocked():
|
|
||||||
return unit.workload_status == 'blocked' and 'ignore-loose-rpf'\
|
|
||||||
in unit.workload_status_message
|
|
||||||
|
|
||||||
await ops_test.model.block_until(blocked, timeout=60)
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import charms.unit_test
|
|
||||||
|
|
||||||
|
|
||||||
charms.unit_test.patch_reactive()
|
|
||||||
charms.unit_test.patch_module('conctl')
|
|
||||||
charms.unit_test.patch_module('charms.leadership')
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
from charmhelpers.core.hookenv import is_leader, config # patched
|
|
||||||
from charmhelpers.core.host import service_running # patched
|
|
||||||
from reactive import calico
|
|
||||||
from unittest.mock import patch, mock_open
|
|
||||||
|
|
||||||
|
|
||||||
def test_series_upgrade():
|
|
||||||
calico.set_state('upgrade.series.in-progress')
|
|
||||||
is_leader.return_value = False
|
|
||||||
service_running.return_value = True
|
|
||||||
assert calico.status.blocked.call_count == 0
|
|
||||||
assert calico.status.waiting.call_count == 0
|
|
||||||
assert calico.status.active.call_count == 0
|
|
||||||
calico.ready()
|
|
||||||
assert calico.status.blocked.call_count == 1
|
|
||||||
assert calico.status.waiting.call_count == 0
|
|
||||||
assert calico.status.active.call_count == 0
|
|
||||||
calico.remove_state('upgrade.series.in-progress')
|
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_loose_rpf_at_exit():
|
|
||||||
# Test the case when charm should be blocked
|
|
||||||
# i.e. when rp filter == 2 and ignore-loose-rpf is false
|
|
||||||
with patch("builtins.open", mock_open(read_data="2")):
|
|
||||||
calico.status.reset_mock()
|
|
||||||
# config.return value returns the config setting for ignore-loose-rpf
|
|
||||||
config.return_value = False
|
|
||||||
calico.ready()
|
|
||||||
assert calico.status.blocked.call_count == 1
|
|
||||||
|
|
||||||
# Test the case when rp filter != 2 and ignore-loose-rpf is false
|
|
||||||
with patch("builtins.open", mock_open(read_data="1")):
|
|
||||||
calico.status.reset_mock()
|
|
||||||
# config.return value returns the config setting for ignore-loose-rpf
|
|
||||||
config.return_value = False
|
|
||||||
calico.ready()
|
|
||||||
assert calico.status.blocked.call_count == 0
|
|
||||||
|
|
||||||
# Test the case when rp filter == 2 and ignore-loose-rpf is true
|
|
||||||
with patch("builtins.open", mock_open(read_data="2")):
|
|
||||||
calico.status.reset_mock()
|
|
||||||
# config.return value returns the config setting for ignore-loose-rpf
|
|
||||||
config.return_value = True
|
|
||||||
calico.ready()
|
|
||||||
assert calico.status.blocked.call_count == 0
|
|
||||||
|
|
||||||
# Test the case when rp filter != 2 and ignore-loose-rpf is true
|
|
||||||
with patch("builtins.open", mock_open(read_data="1")):
|
|
||||||
calico.status.reset_mock()
|
|
||||||
# config.return value returns the config setting for ignore-loose-rpf
|
|
||||||
config.return_value = True
|
|
||||||
calico.ready()
|
|
||||||
assert calico.status.blocked.call_count == 0
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
import json
|
|
||||||
import string
|
|
||||||
from subprocess import CalledProcessError
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import Mock, patch
|
|
||||||
from charms.reactive import endpoint_from_flag
|
|
||||||
|
|
||||||
from charms.layer import kubernetes_common as kc
|
|
||||||
|
|
||||||
|
|
||||||
def test_token_generator():
|
|
||||||
alphanum = string.ascii_letters + string.digits
|
|
||||||
token = kc.token_generator(10)
|
|
||||||
assert len(token) == 10
|
|
||||||
unknown_chars = set(token) - set(alphanum)
|
|
||||||
assert not unknown_chars
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_secret_names(monkeypatch):
|
|
||||||
monkeypatch.setattr(kc, "kubectl", Mock())
|
|
||||||
kc.kubectl.side_effect = [
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
FileNotFoundError,
|
|
||||||
"{}".encode("utf8"),
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"metadata": {"name": "secret-id"},
|
|
||||||
"data": {"username": "dXNlcg=="},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
).encode("utf8"),
|
|
||||||
]
|
|
||||||
assert kc.get_secret_names() == {}
|
|
||||||
assert kc.get_secret_names() == {}
|
|
||||||
assert kc.get_secret_names() == {}
|
|
||||||
assert kc.get_secret_names() == {"user": "secret-id"}
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_rfc1123():
|
|
||||||
alphanum = string.ascii_letters + string.digits
|
|
||||||
token = kc.generate_rfc1123(1000)
|
|
||||||
assert len(token) == 253
|
|
||||||
unknown_chars = set(token) - set(alphanum)
|
|
||||||
assert not unknown_chars
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_secret(monkeypatch):
|
|
||||||
monkeypatch.setattr(kc, "render", Mock())
|
|
||||||
monkeypatch.setattr(kc, "kubectl_manifest", Mock())
|
|
||||||
monkeypatch.setattr(kc, "get_secret_names", Mock())
|
|
||||||
monkeypatch.setattr(kc, "generate_rfc1123", Mock())
|
|
||||||
kc.kubectl_manifest.side_effect = [True, False]
|
|
||||||
kc.get_secret_names.side_effect = [{"username": "secret-id"}, {}]
|
|
||||||
kc.generate_rfc1123.return_value = "foo"
|
|
||||||
assert kc.create_secret("token", "username", "user", "groups")
|
|
||||||
assert kc.render.call_args[1]["context"] == {
|
|
||||||
"groups": "Z3JvdXBz",
|
|
||||||
"password": "dXNlcjo6dG9rZW4=",
|
|
||||||
"secret_name": "secret-id",
|
|
||||||
"secret_namespace": "kube-system",
|
|
||||||
"type": "juju.is/token-auth",
|
|
||||||
"user": "dXNlcg==",
|
|
||||||
"username": "dXNlcm5hbWU=",
|
|
||||||
}
|
|
||||||
assert not kc.create_secret("token", "username", "user", "groups")
|
|
||||||
assert kc.render.call_args[1]["context"] == {
|
|
||||||
"groups": "Z3JvdXBz",
|
|
||||||
"password": "dXNlcjo6dG9rZW4=",
|
|
||||||
"secret_name": "auth-user-foo",
|
|
||||||
"secret_namespace": "kube-system",
|
|
||||||
"type": "juju.is/token-auth",
|
|
||||||
"user": "dXNlcg==",
|
|
||||||
"username": "dXNlcm5hbWU=",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_secret_password(monkeypatch):
|
|
||||||
monkeypatch.setattr(kc, "kubectl", Mock())
|
|
||||||
monkeypatch.setattr(kc, "Path", Mock())
|
|
||||||
monkeypatch.setattr(kc, "yaml", Mock())
|
|
||||||
kc.kubectl.side_effect = [
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
CalledProcessError(1, "none"),
|
|
||||||
FileNotFoundError,
|
|
||||||
json.dumps({}).encode("utf8"),
|
|
||||||
json.dumps({"items": []}).encode("utf8"),
|
|
||||||
json.dumps({"items": []}).encode("utf8"),
|
|
||||||
json.dumps({"items": [{}]}).encode("utf8"),
|
|
||||||
json.dumps({"items": [{"data": {}}]}).encode("utf8"),
|
|
||||||
json.dumps(
|
|
||||||
{"items": [{"data": {"username": "Ym9i", "password": "c2VjcmV0"}}]}
|
|
||||||
).encode("utf8"),
|
|
||||||
json.dumps(
|
|
||||||
{"items": [{"data": {"username": "dXNlcm5hbWU=", "password": "c2VjcmV0"}}]}
|
|
||||||
).encode("utf8"),
|
|
||||||
]
|
|
||||||
kc.yaml.safe_load.side_effect = [
|
|
||||||
{},
|
|
||||||
{"users": None},
|
|
||||||
{"users": []},
|
|
||||||
{"users": [{"user": {}}]},
|
|
||||||
{"users": [{"user": {"token": "secret"}}]},
|
|
||||||
]
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("admin") is None
|
|
||||||
assert kc.get_secret_password("admin") is None
|
|
||||||
assert kc.get_secret_password("admin") is None
|
|
||||||
assert kc.get_secret_password("admin") is None
|
|
||||||
assert kc.get_secret_password("admin") == "secret"
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") is None
|
|
||||||
assert kc.get_secret_password("username") == "secret"
|
|
||||||
|
|
||||||
|
|
||||||
@patch("os.listdir")
|
|
||||||
@patch("os.remove")
|
|
||||||
@patch("os.symlink")
|
|
||||||
def test_configure_default_cni(os_symlink, os_remove, os_listdir):
|
|
||||||
os_listdir.return_value = ["05-default.conflist", "10-cni.conflist"]
|
|
||||||
cni = endpoint_from_flag("cni.available")
|
|
||||||
cni.get_config.return_value = {
|
|
||||||
"cidr": "192.168.0.0/24",
|
|
||||||
"cni-conf-file": "10-cni.conflist",
|
|
||||||
}
|
|
||||||
kc.configure_default_cni("test-cni")
|
|
||||||
os_remove.assert_called_once_with("/etc/cni/net.d/05-default.conflist")
|
|
||||||
os_symlink.assert_called_once_with(
|
|
||||||
"10-cni.conflist", "/etc/cni/net.d/05-default.conflist"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_bind_addrs():
|
|
||||||
response = Path("tests", "data", "ip_addr_json").read_bytes()
|
|
||||||
with patch.object(kc, "check_output", return_value=response):
|
|
||||||
addrs = kc.get_bind_addrs()
|
|
||||||
assert addrs == ["10.246.154.77"]
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
build_dir="$(mktemp -d)"
|
|
||||||
function cleanup { rm -rf "$build_dir"; }
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
charm build . --build-dir "$build_dir"
|
|
||||||
pip install -f "$build_dir/calico/wheelhouse" --no-index --no-cache-dir "$build_dir"/calico/wheelhouse/*
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
[flake8]
|
|
||||||
max-line-length = 88
|
|
||||||
|
|
||||||
[tox]
|
|
||||||
skipsdist = True
|
|
||||||
envlist = lint,unit,integration
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
setenv =
|
|
||||||
PYTHONPATH={toxinidir}:{toxinidir}/lib
|
|
||||||
PYTHONBREAKPOINT=ipdb.set_trace
|
|
||||||
|
|
||||||
[testenv:unit]
|
|
||||||
deps =
|
|
||||||
pyyaml
|
|
||||||
pytest
|
|
||||||
charms.unit_test
|
|
||||||
ipdb
|
|
||||||
commands = pytest --tb native -s {posargs} {toxinidir}/tests/unit
|
|
||||||
|
|
||||||
[testenv:validate-wheelhouse]
|
|
||||||
deps =
|
|
||||||
# Temporarily pin setuptools to avoid the breaking change from 58 until
|
|
||||||
# all dependencies we use have a chance to update. (tempita is the troublemaker for this repo).
|
|
||||||
# See: https://setuptools.readthedocs.io/en/latest/history.html#v58-0-0
|
|
||||||
# and: https://github.com/pypa/setuptools/issues/2784#issuecomment-917663223
|
|
||||||
setuptools<58
|
|
||||||
allowlist_externals = {toxinidir}/tests/validate-wheelhouse.sh
|
|
||||||
commands = {toxinidir}/tests/validate-wheelhouse.sh
|
|
||||||
|
|
||||||
[testenv:integration]
|
|
||||||
deps =
|
|
||||||
pytest
|
|
||||||
pytest-operator
|
|
||||||
aiohttp
|
|
||||||
ipdb
|
|
||||||
git+https://github.com/canonical/kubernetes-rapper@main#egg=kubernetes-wrapper
|
|
||||||
# tox only passes through the upper-case versions by default, but some
|
|
||||||
# programs, such as wget or pip, only honor the lower-case versions
|
|
||||||
passenv = http_proxy https_proxy no_proxy
|
|
||||||
commands = pytest --asyncio-mode=auto --tb native --show-capture=no --log-cli-level=INFO -s {posargs} {toxinidir}/tests/integration
|
|
||||||
|
|
||||||
[testenv:lint]
|
|
||||||
deps =
|
|
||||||
flake8
|
|
||||||
commands =
|
|
||||||
flake8 {toxinidir}/reactive {toxinidir}/lib {toxinidir}/tests
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
1.24+ck1
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
# layer:basic
|
|
||||||
# pip is pinned to <19.0 to avoid https://github.com/pypa/pip/issues/6164
|
|
||||||
# even with installing setuptools before upgrading pip ends up with pip seeing
|
|
||||||
# the older setuptools at the system level if include_system_packages is true
|
|
||||||
pip>=18.1,<19.0;python_version < '3.8'
|
|
||||||
pip;python_version >= '3.8'
|
|
||||||
# pin Jinja2, PyYAML and MarkupSafe to the last versions supporting python 3.5
|
|
||||||
# for trusty
|
|
||||||
Jinja2==2.10;python_version >= '3.0' and python_version <= '3.4' # py3 trusty
|
|
||||||
Jinja2==2.11;python_version == '2.7' or python_version == '3.5' # py27, py35
|
|
||||||
Jinja2;python_version >= '3.6' # py36 and on
|
|
||||||
|
|
||||||
PyYAML==5.2;python_version >= '3.0' and python_version <= '3.4' # py3 trusty
|
|
||||||
PyYAML<5.4;python_version == '2.7' or python_version >= '3.5' # all else
|
|
||||||
|
|
||||||
MarkupSafe<2.0.0;python_version < '3.6'
|
|
||||||
MarkupSafe<2.1.0;python_version == '3.6' # Just for python 3.6
|
|
||||||
MarkupSafe;python_version >= '3.7' # newer pythons
|
|
||||||
|
|
||||||
setuptools<42;python_version < '3.8'
|
|
||||||
setuptools;python_version >= '3.8'
|
|
||||||
setuptools-scm<=1.17.0;python_version < '3.8'
|
|
||||||
setuptools-scm;python_version >= '3.8'
|
|
||||||
flit_core;python_version >= '3.8'
|
|
||||||
charmhelpers>=0.4.0,<2.0.0
|
|
||||||
charms.reactive>=0.1.0,<2.0.0
|
|
||||||
wheel<0.34;python_version < '3.8'
|
|
||||||
wheel;python_version >= '3.8'
|
|
||||||
# pin netaddr to avoid pulling importlib-resources
|
|
||||||
netaddr<=0.7.19
|
|
||||||
|
|
||||||
# calico
|
|
||||||
git+https://github.com/charmed-kubernetes/conctl@e1e17369#egg=conctl
|
|
||||||
# pin click to avoid bringing in incompatible setuptools>=42
|
|
||||||
click<8.0
|
|
||||||
|
|
||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue