update:charmed-kubernetes-657

This commit is contained in:
AnonSaber 2023-04-10 07:43:16 +00:00
parent 4b23a07088
commit 5bac7f1b31
103 changed files with 4068 additions and 2 deletions

View File

@ -2,11 +2,14 @@
cs:~containers/charmed-kubernetes-657
```Bash
charm pull cs:~containers/containerd-119
charm pull cs:~containers/kubeapi-load-balancer-786
charm pull cs:~containers/etcd-583
charm pull cs:~containers/easyrsa-373
charm pull cs:~containers/kubernetes-master-990
charm pull cs:~containers/kubernetes-worker-757
charm pull cs:~containers/containerd-119
charm pull cs:~containers/calico-812
# Extend
charm pull cs:~containers/kubeapi-load-balancer-786
charm pull cs:~containers/keepalived-85
charm pull cs:~containers/coredns-20
```

546
keepalived/.build.manifest Normal file
View File

@ -0,0 +1,546 @@
{
"layers": [
{
"branch": "refs/heads/master\nrefs/heads/stable",
"rev": "fcdcea4e5de3e1556c24e6704607862d0ba00a56",
"url": "layer:options"
},
{
"branch": "refs/heads/stable",
"rev": "0d10732a6e14ea2f940a35ab61425a97c5db6a16",
"url": "layer:basic"
},
{
"branch": "refs/heads/master\nrefs/heads/stable",
"rev": "a7d7b6423db37a47611310039e6ed1929c0a2eab",
"url": "layer:status"
},
{
"branch": "refs/heads/stable",
"rev": "348a4a770068a42afec8230ea167346689baafd2",
"url": "keepalived"
},
{
"branch": "refs/heads/master\nrefs/heads/stable",
"rev": "4e2f90052b3c02031d09f10900c9e9cb22565dee",
"url": "interface:juju-info"
},
{
"branch": "refs/heads/master\nrefs/heads/stable",
"rev": "5021f8a23f6e6e4cc449d2d02f2d8cb99763ec27",
"url": "interface:public-address"
},
{
"branch": "refs/heads/master\nrefs/heads/stable",
"rev": "632131b1f122daf6fb601fd4c9f1e4dbb1a92e09",
"url": "interface:http"
}
],
"signatures": {
".build.manifest": [
"build",
"dynamic",
"unchecked"
],
".gitignore": [
"layer:status",
"static",
"315971ad9cc5d6ada2391f0940e1800149b211a18be3c7a8f396735d7978702b"
],
".travis/profile-update.yaml": [
"layer:basic",
"static",
"731e20aa59bf61c024d317ad630e478301a9386ccc0afe56e6c1c09db07ac83b"
],
"LICENSE": [
"layer:status",
"static",
"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"
],
"Makefile": [
"layer:basic",
"static",
"b7ab3a34e5faf79b96a8632039a0ad0aa87f2a9b5f0ba604e007cafb22190301"
],
"README.md": [
"keepalived",
"static",
"c65fa846ba9a75aab5a5d9f1a38818ed806df4246d58fc1034c7f21cb168f470"
],
"bin/charm-env": [
"layer:basic",
"static",
"fb6a20fac4102a6a4b6ffe903fcf666998f9a95a3647e6f9af7a1eeb44e58fd5"
],
"bin/layer_option": [
"layer:options",
"static",
"e959bf29da4c5edff28b2602c24113c4df9e25cdc9f2aa3b5d46c8577b2a40cc"
],
"config.yaml": [
"keepalived",
"dynamic",
"d04d990bb872982a66430cf14b74a864df38eb0e96dd54b80d7f28c83b599249"
],
"copyright": [
"layer:status",
"static",
"7c0e36e618a8544faaaa3f8e0533c2f1f4a18bcacbdd8b99b537742e6b587d58"
],
"copyright.layer-basic": [
"layer:basic",
"static",
"f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629"
],
"copyright.layer-options": [
"layer:options",
"static",
"f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629"
],
"docs/status.md": [
"layer:status",
"static",
"975dec9f8c938196e102e954a80226bda293407c4e5ae857c118bf692154702a"
],
"hooks/config-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/hook.template": [
"layer:basic",
"static",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/install": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/juju-info-relation-broken": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/juju-info-relation-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/juju-info-relation-created": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/juju-info-relation-departed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/juju-info-relation-joined": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/lb-sink-relation-broken": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/lb-sink-relation-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/lb-sink-relation-created": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/lb-sink-relation-departed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/lb-sink-relation-joined": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/leader-elected": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/leader-settings-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/loadbalancer-relation-broken": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/loadbalancer-relation-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/loadbalancer-relation-created": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/loadbalancer-relation-departed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/loadbalancer-relation-joined": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/post-series-upgrade": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/pre-series-upgrade": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/relations/http/.gitignore": [
"interface:http",
"static",
"83b4ca18cc39800b1d260b5633cd0252e21501b21e7c33e718db44f1a68a09b8"
],
"hooks/relations/http/README.md": [
"interface:http",
"static",
"9c95320ad040745374fc03e972077f52c27e07eb0386ec93ae19bd50dca24c0d"
],
"hooks/relations/http/__init__.py": [
"interface:http",
"static",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
],
"hooks/relations/http/interface.yaml": [
"interface:http",
"static",
"d0b64038b85b7791ee4f3a42d73ffc8c208f206f73f899cbf33a519d12f9ad13"
],
"hooks/relations/http/provides.py": [
"interface:http",
"static",
"8c72cd8a5a6ea24f53b6dba11f4353c75265bfa7d3ecc2dd096c8963eab8c877"
],
"hooks/relations/http/requires.py": [
"interface:http",
"static",
"76cc886368eaf9c2403a6dc46b40531c3f4eaf67b08829f890c57cb645430abd"
],
"hooks/relations/juju-info/.gitignore": [
"interface:juju-info",
"static",
"315971ad9cc5d6ada2391f0940e1800149b211a18be3c7a8f396735d7978702b"
],
"hooks/relations/juju-info/README.md": [
"interface:juju-info",
"static",
"745aade1bda4e3cb7b07109c8c7560b2f56e076fcd6e8298465c7f66a970cfa0"
],
"hooks/relations/juju-info/__init__.py": [
"interface:juju-info",
"static",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
],
"hooks/relations/juju-info/docs/peers.md": [
"interface:juju-info",
"static",
"710be74f2673dd71a8a41d51417b109bfe68782cf527f100138b628fce531c87"
],
"hooks/relations/juju-info/docs/provides.md": [
"interface:juju-info",
"static",
"f986015a7c6790057f694d91ccda2c281cf07634a2dfbe6607e67768a9d9f7b0"
],
"hooks/relations/juju-info/docs/requires.md": [
"interface:juju-info",
"static",
"bbe0d5b69bfee4992adcfcbf974703e351027f23062dda20845616cb49878010"
],
"hooks/relations/juju-info/interface.yaml": [
"interface:juju-info",
"static",
"0d0057ed0bda75157e8314a1259e1a8bb883d2f738ad1b731177e821b5b1b542"
],
"hooks/relations/juju-info/make_docs": [
"interface:juju-info",
"static",
"136818e53eb2ee7c5a178c5793d1a06811bebfbeb8875b9fc8e425d93ad9b433"
],
"hooks/relations/juju-info/peers.py": [
"interface:juju-info",
"static",
"3a778e2d89f736caf4e1e743ebc96d9ce53af7f3e0bef4b706dd74e5eb373b6e"
],
"hooks/relations/juju-info/provides.py": [
"interface:juju-info",
"static",
"3a778e2d89f736caf4e1e743ebc96d9ce53af7f3e0bef4b706dd74e5eb373b6e"
],
"hooks/relations/juju-info/pydocmd.yml": [
"interface:juju-info",
"static",
"78f3a1376cc4ef1c297a239104203f70701da29d089fefceb5323503422ee848"
],
"hooks/relations/juju-info/requires.py": [
"interface:juju-info",
"static",
"3a778e2d89f736caf4e1e743ebc96d9ce53af7f3e0bef4b706dd74e5eb373b6e"
],
"hooks/relations/juju-info/tox.ini": [
"interface:juju-info",
"static",
"caeace713d04686331d19b2d466e066e4123bd9197152ab99deb1a29a1501502"
],
"hooks/relations/public-address/README.md": [
"interface:public-address",
"static",
"7225effe61bfd8571447b8b685a2ecb52be17431b3066a5306330954c4cb064d"
],
"hooks/relations/public-address/__init__.py": [
"interface:public-address",
"static",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
],
"hooks/relations/public-address/interface.yaml": [
"interface:public-address",
"static",
"49d6777a54aa84c7d3be8d531be237564e90f2e4cb2be05ef5617a372a382340"
],
"hooks/relations/public-address/provides.py": [
"interface:public-address",
"static",
"7c99b0fe987d38773ed3e67c0378fdb78748c04d6895489cd4bca40aaeb051b2"
],
"hooks/relations/public-address/requires.py": [
"interface:public-address",
"static",
"d6a7c6c0762d29a5db19afb4cf82af50812988d5e19a3a48fcbe8b0f6fec12a5"
],
"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"
],
"hooks/website-relation-broken": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/website-relation-changed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/website-relation-created": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/website-relation-departed": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"hooks/website-relation-joined": [
"layer:basic",
"dynamic",
"2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7"
],
"icon.svg": [
"keepalived",
"static",
"7c51f3a3274f85f40de905124d32227711d3eea8e81bc9b14d6962471026f6af"
],
"layer.yaml": [
"keepalived",
"dynamic",
"900ed82afe183b924d8ebe49ad3d886b1967b35094e2e6431359619c19e6b9cc"
],
"lib/charms/layer/__init__.py": [
"layer:basic",
"static",
"dfe0d26c6bf409767de6e2546bc648f150e1b396243619bad3aa0553ab7e0e6f"
],
"lib/charms/layer/basic.py": [
"layer:basic",
"static",
"3126b5754ad39402ee27e64527044ddd231ed1cd137fcedaffb51e63a635f108"
],
"lib/charms/layer/execd.py": [
"layer:basic",
"static",
"fda8bd491032db1db8ddaf4e99e7cc878c6fb5432efe1f91cadb5b34765d076d"
],
"lib/charms/layer/options.py": [
"layer:options",
"static",
"8ae7a07d22542fc964f2d2bee8219d1c78a68dace70a1b38d36d4aea47b1c3b2"
],
"lib/charms/layer/status.py": [
"layer:status",
"static",
"d560a5e07b2e5f2b0f25f30e1f0278b06f3f90c01e4dbad5c83d71efc79018c6"
],
"make_docs": [
"layer:status",
"static",
"c990f55c8e879793a62ed8464ee3d7e0d7d2225fdecaf17af24b0df0e2daa8c1"
],
"metadata.yaml": [
"keepalived",
"dynamic",
"dac11c4ab89f07c6202bde9cd8a88843df4b4f130c74a24cebb2e1806c733094"
],
"pydocmd.yml": [
"layer:status",
"static",
"11d9293901f32f75f4256ae4ac2073b92ce1d7ef7b6c892ba9fbb98690a0b330"
],
"reactive/__init__.py": [
"layer:basic",
"static",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
],
"reactive/keepalived.py": [
"keepalived",
"static",
"496473d95ce5f261a7119431236a68675a93ab2f35db9c355591f77d598e8caf"
],
"reactive/status.py": [
"layer:status",
"static",
"30207fc206f24e91def5252f1c7f7c8e23c0aed0e93076babf5e03c05296d207"
],
"requirements.txt": [
"layer:basic",
"static",
"a00f75d80849e5b4fc5ad2e7536f947c25b1a4044b341caa8ee87a92d3a4c804"
],
"templates/50-keepalived.conf": [
"keepalived",
"static",
"0feef827d3edbbdb047f4efa9fcb81c8b88600c07856f00eedeebf6d43010e96"
],
"templates/keepalived.conf": [
"keepalived",
"static",
"a8a28fb2ad0195f78dece2f6594e5c2e826340e33d01bd71c1b9ededd6bb0955"
],
"tox.ini": [
"layer:status",
"static",
"2669a78e8e51c1606874e1cc97ca99e660ff547a79592572a38a268d99b25b67"
],
"version": [
"keepalived",
"dynamic",
"a3bff55840dcf7d1866186038c890d78b43261e77ecf1e0f6378daf4a7fe3e21"
],
"wheelhouse.txt": [
"layer:basic",
"dynamic",
"44b8a3ab6ccaf3a81c8a96526a285462e01964e6090fd40104f3a087bab43c0c"
],
"wheelhouse/Jinja2-2.10.1.tar.gz": [
"layer:basic",
"dynamic",
"065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013"
],
"wheelhouse/MarkupSafe-1.1.1.tar.gz": [
"layer:basic",
"dynamic",
"29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"
],
"wheelhouse/PyYAML-5.2.tar.gz": [
"layer:basic",
"dynamic",
"c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"
],
"wheelhouse/Tempita-0.5.2.tar.gz": [
"__pip__",
"dynamic",
"cacecf0baa674d356641f1d406b8bff1d756d739c46b869a54de515d08e6fc9c"
],
"wheelhouse/charmhelpers-0.20.22.tar.gz": [
"layer:basic",
"dynamic",
"b7550108118ce4f87488343384441797777d0da746e1346ed4e6361b4eab0ddb"
],
"wheelhouse/charms.reactive-1.4.1.tar.gz": [
"layer:basic",
"dynamic",
"bba21b4fd40b26c240c9ef2aa10c6fdf73592031c68591da4e7ccc46ca9cb616"
],
"wheelhouse/netaddr-0.7.19.tar.gz": [
"layer:basic",
"dynamic",
"38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd"
],
"wheelhouse/pbr-5.6.0.tar.gz": [
"__pip__",
"dynamic",
"42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"
],
"wheelhouse/pip-18.1.tar.gz": [
"layer:basic",
"dynamic",
"c0a292bd977ef590379a3f05d7b7f65135487b67470f6281289a94e015650ea1"
],
"wheelhouse/pyaml-20.4.0.tar.gz": [
"__pip__",
"dynamic",
"29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71"
],
"wheelhouse/setuptools-41.6.0.zip": [
"layer:basic",
"dynamic",
"6afa61b391dcd16cb8890ec9f66cc4015a8a31a6e1c2b4e0c464514be1a3d722"
],
"wheelhouse/setuptools_scm-1.17.0.tar.gz": [
"layer:basic",
"dynamic",
"70a4cf5584e966ae92f54a764e6437af992ba42ac4bca7eb37cc5d02b98ec40a"
],
"wheelhouse/six-1.16.0.tar.gz": [
"__pip__",
"dynamic",
"1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"
],
"wheelhouse/wheel-0.33.6.tar.gz": [
"layer:basic",
"dynamic",
"10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646"
]
}
}

2
keepalived/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.tox
__pycache__

View File

@ -0,0 +1,12 @@
config: {}
description: Default LXD profile - updated
devices:
eth0:
name: eth0
parent: lxdbr0
nictype: bridged
type: nic
root:
path: /
pool: default
type: disk

202
keepalived/LICENSE Normal file
View File

@ -0,0 +1,202 @@
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.

24
keepalived/Makefile Normal file
View File

@ -0,0 +1,24 @@
#!/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

8
keepalived/README.md Normal file
View File

@ -0,0 +1,8 @@
# Keepalived Charm
[Keepalived](http://www.keepalived.org/) is software which provides high
availability by assigning two or more nodes a virtual IP and monitoring
those nodes, failing over when one goes down.
This charm is maintained along with the components of Charmed Kubernetes. For full information,
please visit the [official Charmed Kubernetes docs](https://www.ubuntu.com/kubernetes/docs/charm-keepalived).

107
keepalived/bin/charm-env Executable file
View File

@ -0,0 +1,107 @@
#!/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

22
keepalived/bin/layer_option Executable file
View File

@ -0,0 +1,22 @@
#!/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)

35
keepalived/config.yaml Normal file
View File

@ -0,0 +1,35 @@
"options":
"virtual_ip":
"type": "string"
"default": ""
"description": |
Virtual IP/netmask that will be moved between instances,
e.g.: 10.1.2.3/16
"vip_hostname":
"type": "string"
"default": ""
"description": |
A VIP hostname to pass to clients.
"port":
"type": "int"
"default": !!int "443"
"description": |
A port to pass to clients.
"router_id":
"type": "int"
"default": !!int "23"
"description": |
Virtual router identifier - a number between 1 and 255
that's unique within the network segment
"network_interface":
"type": "string"
"default": ""
"description": |
Network interface name for the VIP. The default value is
the result of running the following command:
`route | grep default | head -n 1 | awk {'print $8'}`.
"healthcheck_interval":
"type": "int"
"default": !!int "2"
"description": |
vrrp_script-based health-check interval, in seconds

16
keepalived/copyright Normal file
View File

@ -0,0 +1,16 @@
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.

View File

@ -0,0 +1,16 @@
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.

View File

@ -0,0 +1,16 @@
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.

91
keepalived/docs/status.md Normal file
View File

@ -0,0 +1,91 @@
<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.

22
keepalived/hooks/config-changed Executable file
View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

22
keepalived/hooks/install Executable file
View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

22
keepalived/hooks/leader-elected Executable file
View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,5 @@
# Emacs save files
*~
\#*\#
.\#*

View File

@ -0,0 +1,68 @@
# Overview
This interface layer implements the basic form of the `http` interface protocol,
which is used for things such as reverse-proxies, load-balanced servers, REST
service discovery, et cetera.
# Usage
## Provides
By providing the `http` interface, your charm is providing an HTTP server that
can be load-balanced, reverse-proxied, used as a REST endpoint, etc.
Your charm need only provide the port on which it is serving its content, as
soon as the `{relation_name}.available` state is set:
```python
@when('website.available')
def configure_website(website):
website.configure(port=hookenv.config('port'))
```
## Requires
By requiring the `http` interface, your charm is consuming one or more HTTP
servers, as a REST endpoint, to load-balance a set of servers, etc.
Your charm should respond to the `{relation_name}.available` state, which
indicates that there is at least one HTTP server connected.
The `services()` method returns a list of available HTTP services and their
associated hosts and ports.
The return value is a list of dicts of the following form:
```python
[
{
'service_name': name_of_service,
'hosts': [
{
'hostname': address_of_host,
'port': port_for_host,
},
# ...
],
},
# ...
]
```
A trivial example of handling this interface would be:
```python
from charms.reactive.helpers import data_changed
@when('reverseproxy.available')
def update_reverse_proxy_config(reverseproxy):
services = reverseproxy.services()
if not data_changed('reverseproxy.services', services):
return
for service in services:
for host in service['hosts']:
hookenv.log('{} has a unit {}:{}'.format(
services['service_name'],
host['hostname'],
host['port']))
```

View File

@ -0,0 +1,4 @@
name: http
summary: Basic HTTP interface
version: 1
repo: https://git.launchpad.net/~bcsaller/charms/+source/http

View File

@ -0,0 +1,67 @@
import json
from charmhelpers.core import hookenv
from charms.reactive import when, when_not
from charms.reactive import set_flag, clear_flag
from charms.reactive import Endpoint
class HttpProvides(Endpoint):
@when('endpoint.{endpoint_name}.joined')
def joined(self):
set_flag(self.expand_name('{endpoint_name}.available'))
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
def get_ingress_address(self, rel_id=None):
# If no rel_id is provided, we fallback to the first one
if rel_id is None:
rel_id = self.relations[0].relation_id
return hookenv.ingress_address(rel_id, hookenv.local_unit())
def configure(self, port, private_address=None, hostname=None):
''' configure the address(es). private_address and hostname can
be None, a single string address/hostname, or a list of addresses
and hostnames. Note that if a list is passed, it is assumed both
private_address and hostname are either lists or None '''
for relation in self.relations:
ingress_address = self.get_ingress_address(relation.relation_id)
if type(private_address) is list or type(hostname) is list:
# build 3 lists to zip together that are the same length
length = max(len(private_address), len(hostname))
p = [port] * length
a = private_address + [ingress_address] *\
(length - len(private_address))
h = hostname + [ingress_address] * (length - len(hostname))
zipped_list = zip(p, a, h)
# now build an array of dictionaries from that in the desired
# format for the interface
data_list = [{'hostname': h, 'port': p, 'private-address': a}
for p, a, h in zipped_list]
# for backwards compatibility, we just send a single entry
# and have an array of dictionaries in a field of that
# entry for the other entries.
data = data_list.pop(0)
data['extended_data'] = json.dumps(data_list)
relation.to_publish_raw.update(data)
else:
relation.to_publish_raw.update({
'hostname': hostname or ingress_address,
'private-address': private_address or ingress_address,
'port': port,
})
def set_remote(self, **kwargs):
# NB: This method provides backwards compatibility for charms that
# called RelationBase.set_remote. Most commonly, this was done by
# charms that needed to pass reverse proxy stanzas to http proxies.
# This type of interaction with base relation classes is discouraged,
# and should be handled with logic encapsulated in appropriate
# interfaces. Eventually, this method will be deprecated in favor of
# that behavior.
for relation in self.relations:
relation.to_publish_raw.update(kwargs)

View File

@ -0,0 +1,76 @@
import json
from charms.reactive import when, when_not
from charms.reactive import set_flag, clear_flag
from charms.reactive import Endpoint
class HttpRequires(Endpoint):
@when('endpoint.{endpoint_name}.changed')
def changed(self):
if any(unit.received_raw['port'] for unit in self.all_joined_units):
set_flag(self.expand_name('{endpoint_name}.available'))
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
def services(self):
"""
Returns a list of available HTTP services and their associated hosts
and ports.
The return value is a list of dicts of the following form::
[
{
'service_name': name_of_service,
'hosts': [
{
'hostname': address_of_host,
'private-address': private_address_of_host,
'port': port_for_host,
},
# ...
],
},
# ...
]
"""
def build_service_host(data):
private_address = data['private-address']
host = data['hostname'] or private_address
if host and data['port']:
return (host, private_address, data['port'])
else:
return None
services = {}
for relation in self.relations:
service_name = relation.application_name
service = services.setdefault(service_name, {
'service_name': service_name,
'hosts': [],
})
host_set = set()
for unit in relation.joined_units:
data = unit.received_raw
host = build_service_host(data)
if host:
host_set.add(host)
# if we have extended data, add it
if 'extended_data' in data:
for ed in json.loads(data['extended_data']):
host = build_service_host(ed)
if host:
host_set.add(host)
service['hosts'] = [
{'hostname': h, 'private-address': pa, 'port': p}
for h, pa, p in sorted(host_set)
]
ret = [s for s in services.values() if s['hosts']]
return ret

View File

@ -0,0 +1,2 @@
.tox
__pycache__

View File

@ -0,0 +1,39 @@
# Juju-Info Interface
The juju info interface is a special and implicit relationship that works with
any charm. It is mainly useful for subordinate charms that can add
functionality to any exisiting machine without the host charm being aware of
it.
### Flags
`{{endpoint_name}}.connected`
Note: This flag keys off of what the charm author names the relationship
endpoint, which should *not* be the name of the interface:
An example of a properly implemented relationship would resemble the following:
```yaml
requires:
host-system:
interface: juju-info
```
This might then be used in your charm would like:
```python
@when_any('host-system.connected')
def handle_host():
host = endpoint_from_flag('host-system.connected')
for address in host.addresses:
hookenv.log('Connected to: {}'.format(address))
```
## Reference
* [Requires API documentation](docs/requires.md)
* [Provides API documentation](docs/provides.md)
* [Peers API documentation](docs/peers.md)

View File

@ -0,0 +1,52 @@
<h1 id="peers">peers</h1>
<h1 id="peers.JujuInfoClient">JujuInfoClient</h1>
```python
JujuInfoClient(endpoint_name, relation_ids=None)
```
<h2 id="peers.JujuInfoClient.addresses">addresses</h2>
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="peers.JujuInfoClient.addresses_map">addresses_map</h2>
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="peers.JujuInfoClient.unit_count">unit_count</h2>
Number of joined units.
<h2 id="peers.JujuInfoClient.get_private_address">get_private_address</h2>
```python
JujuInfoClient.get_private_address()
```
Deprecated.

View File

@ -0,0 +1,52 @@
<h1 id="provides">provides</h1>
<h1 id="provides.JujuInfoClient">JujuInfoClient</h1>
```python
JujuInfoClient(endpoint_name, relation_ids=None)
```
<h2 id="provides.JujuInfoClient.addresses">addresses</h2>
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="provides.JujuInfoClient.addresses_map">addresses_map</h2>
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="provides.JujuInfoClient.unit_count">unit_count</h2>
Number of joined units.
<h2 id="provides.JujuInfoClient.get_private_address">get_private_address</h2>
```python
JujuInfoClient.get_private_address()
```
Deprecated.

View File

@ -0,0 +1,52 @@
<h1 id="requires">requires</h1>
<h1 id="requires.JujuInfoClient">JujuInfoClient</h1>
```python
JujuInfoClient(endpoint_name, relation_ids=None)
```
<h2 id="requires.JujuInfoClient.addresses">addresses</h2>
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="requires.JujuInfoClient.addresses_map">addresses_map</h2>
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
<h2 id="requires.JujuInfoClient.unit_count">unit_count</h2>
Number of joined units.
<h2 id="requires.JujuInfoClient.get_private_address">get_private_address</h2>
```python
JujuInfoClient.get_private_address()
```
Deprecated.

View File

@ -0,0 +1,4 @@
name: juju-info
summary: Used in subordinate charms (rarely)
version: 1
maintainer: "Charles Butler <charles.butler@canonical.com>"

View File

@ -0,0 +1,21 @@
#!.tox/py3/bin/python
import sys
from shutil import rmtree
from unittest.mock import patch
import pydocmd.__main__
with patch('charmhelpers.core.hookenv.metadata') as metadata:
metadata.return_value = {
'requires': {'juju-info': {'interface': 'juju-info'}},
'provides': {'juju-info': {'interface': 'juju-info'}},
'peers': {'juju-info': {'interface': 'juju-info'}},
}
sys.path.insert(0, '.')
print(sys.argv)
if len(sys.argv) == 1:
sys.argv.extend(['build'])
pydocmd.__main__.main()
rmtree('_build')

View File

@ -0,0 +1,81 @@
#!/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 Endpoint
from charms.reactive import when, when_not, set_flag, clear_flag
class JujuInfoClient(Endpoint):
@when('endpoint.{endpoint_name}.joined')
def changed(self):
set_flag(self.expand_name('{endpoint_name}.connected'))
set_flag(self.expand_name('{endpoint_name}.available'))
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
clear_flag(self.expand_name('{endpoint_name}.connected'))
def get_private_address(self):
"""
Deprecated.
"""
return self.all_joined_units[0]['private-address']
@property
def unit_count(self):
"""
Number of joined units.
"""
return len(self.all_joined_units)
@property
def addresses(self):
"""
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
addrs = {u.received_raw['ingress-address']
for u in self.all_joined_units}
return list(sorted(addrs))
@property
def addresses_map(self):
"""
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
return {
r.application_name: {
u.unit_name: u.received_raw['ingress-address']
for u in r.joined_units
} for r in self.relations
}

View File

@ -0,0 +1,81 @@
#!/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 Endpoint
from charms.reactive import when, when_not, set_flag, clear_flag
class JujuInfoClient(Endpoint):
@when('endpoint.{endpoint_name}.joined')
def changed(self):
set_flag(self.expand_name('{endpoint_name}.connected'))
set_flag(self.expand_name('{endpoint_name}.available'))
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
clear_flag(self.expand_name('{endpoint_name}.connected'))
def get_private_address(self):
"""
Deprecated.
"""
return self.all_joined_units[0]['private-address']
@property
def unit_count(self):
"""
Number of joined units.
"""
return len(self.all_joined_units)
@property
def addresses(self):
"""
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
addrs = {u.received_raw['ingress-address']
for u in self.all_joined_units}
return list(sorted(addrs))
@property
def addresses_map(self):
"""
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
return {
r.application_name: {
u.unit_name: u.received_raw['ingress-address']
for u in r.joined_units
} for r in self.relations
}

View File

@ -0,0 +1,19 @@
site_name: 'Juju Info Interface'
generate:
- requires.md:
- requires
- requires.JujuInfoClient+
- provides.md:
- provides
- provides.JujuInfoClient+
- peers.md:
- peers
- peers.JujuInfoClient+
pages:
- Requires: requires.md
- Provides: provides.md
- Peers: peers.md
gens_dir: docs

View File

@ -0,0 +1,81 @@
#!/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 Endpoint
from charms.reactive import when, when_not, set_flag, clear_flag
class JujuInfoClient(Endpoint):
@when('endpoint.{endpoint_name}.joined')
def changed(self):
set_flag(self.expand_name('{endpoint_name}.connected'))
set_flag(self.expand_name('{endpoint_name}.available'))
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
clear_flag(self.expand_name('{endpoint_name}.connected'))
def get_private_address(self):
"""
Deprecated.
"""
return self.all_joined_units[0]['private-address']
@property
def unit_count(self):
"""
Number of joined units.
"""
return len(self.all_joined_units)
@property
def addresses(self):
"""
A flat list of all addresses received from related apps / units.
This list is de-duplicated and sorted by address, so it will be stable
for change comparison. If you need to know which app / unit an address
comes from, see `received_addresses_map`.
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
addrs = {u.received_raw['ingress-address']
for u in self.all_joined_units}
return list(sorted(addrs))
@property
def addresses_map(self):
"""
A nested dictionary of all addresses received from related apps / units
by app name then unit name.
For example::
{
'app1': {
'app1/0': '10.0.0.1',
'app1/1': '10.0.0.2',
}
}
Note: This uses ingress-address, so it will work with cross-model
relations.
"""
return {
r.application_name: {
u.unit_name: u.received_raw['ingress-address']
for u in r.joined_units
} for r in self.relations
}

View File

@ -0,0 +1,14 @@
[tox]
envlist = py3
skipsdist = true
[testenv]
basepython=python3
envdir={toxworkdir}/py3
deps=
pytest
charms.reactive
git+https://github.com/NiklasRosenstein/pydoc-markdown#egg=pydoc-markdown
[testenv:docs]
commands=python make_docs

View File

@ -0,0 +1,59 @@
# Overview
This interface layer implements a public address protocol useful for load
balancers and their subordinates. The load balancers (providers) set their
own public address and port, which is then available to the subordinates
(requirers).
# Usage
## Provides
By providing the `public-address` interface, your charm is providing an HTTP
server that can load-balance for another HTTP based service.
Your charm need only provide the address and port on which it is serving its
content, as soon as the `{relation_name}.available` state is set:
```python
from charmhelpers.core import hookenv
@when('website.available')
def configure_website(website):
website.set_address_port(hookenv.unit_get('public-address'), hookenv.config('port'))
```
## Requires
By requiring the `public-address` interface, your charm is consuming one or
more HTTP servers, to load-balance a set of servers, etc.
Your charm should respond to the `{relation_name}.available` state, which
indicates that there is at least one HTTP server connected.
The `get_addresses_ports()` method returns a list of available addresses and
ports.
The return value is a list of dicts of the following form:
```python
[
{
'public-address': address_of_host,
'port': port_for_host,
},
# ...
]
```
A trivial example of handling this interface would be:
```python
from charmhelpers.core import hookenv
@when('loadbalancer.available')
def update_reverse_proxy_config(loadbalancer):
hosts = loadbalancer.get_addresses_ports()
for host in hosts:
hookenv.log('The loadbalancer for this unit is {}:{}'.format(
host['public-address'],
host['port']))
```

View File

@ -0,0 +1,4 @@
name: public-address
summary: A basic interface to provide the public address for load balancers.
version: 1
repo: https://githb.com/juju-solutions/interface-public-address.git

View File

@ -0,0 +1,60 @@
import json
from charms.reactive import toggle_flag
from charms.reactive import Endpoint
class PublicAdddressProvides(Endpoint):
def manage_flags(self):
toggle_flag(self.expand_name('{endpoint_name}.available'),
self.is_joined)
def set_address_port(self, address, port, relation=None):
if relation is None:
# no relation specified, so send the same data to everyone
relations = self.relations
else:
# specific relation given, so only send the data to that one
relations = [relation]
if type(address) is list:
# build 2 lists to zip together that are the same length
length = len(address)
p = [port] * length
combined = zip(address, p)
clients = [{'public-address': a, 'port': p}
for a, p in combined]
# for backwards compatibility, we just send a single entry
# and have an array of dictionaries in a field of that
# entry for the other entries.
first = clients.pop(0)
first['extended_data'] = json.dumps(clients)
for relation in relations:
relation.to_publish_raw.update(first)
else:
for relation in relations:
relation.to_publish_raw.update({'public-address': address,
'port': port})
@property
def requests(self):
return [Request(rel) for rel in self.relations]
class Request:
def __init__(self, rel):
self.rel = rel
@property
def application_name(self):
return self.rel.application_name
@property
def members(self):
return [(u.received_raw.get('ingress-address',
u.received_raw['private-address']),
u.received_raw.get('port', '6443'))
for u in self.rel.joined_units]
def set_address_port(self, address, port):
self.rel.endpoint.set_address_port(address, port, self.rel)

View File

@ -0,0 +1,44 @@
import json
from charms.reactive import toggle_flag, Endpoint
class PublicAddressRequires(Endpoint):
def manage_flags(self):
toggle_flag(self.expand_name('{endpoint_name}.available'),
len(self.get_addresses_ports()) > 0)
def set_backend_port(self, port):
"""
Set the port that the backend service is listening on.
Defaults to 6443 if not set.
"""
for rel in self.relations:
rel.to_publish_raw['port'] = str(port)
def get_addresses_ports(self):
'''Returns a list of available HTTP providers and their associated
public addresses and ports.
The return value is a list of dicts of the following form::
[
{
'public-address': address_for_frontend,
'port': port_for_frontend,
},
# ...
]
'''
hosts = set()
for relation in self.relations:
for unit in relation.joined_units:
data = unit.received_raw
hosts.add((data['public-address'], data['port']))
if 'extended_data' in data:
for ed in json.loads(data['extended_data']):
hosts.add((ed['public-address'], ed['port']))
return [{'public-address': pa, 'port': p}
for pa, p in sorted(host for host in hosts
if None not in host)]

22
keepalived/hooks/start Executable file
View File

@ -0,0 +1,22 @@
#!/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()

22
keepalived/hooks/stop Executable file
View File

@ -0,0 +1,22 @@
#!/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()

22
keepalived/hooks/update-status Executable file
View File

@ -0,0 +1,22 @@
#!/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()

22
keepalived/hooks/upgrade-charm Executable file
View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

View File

@ -0,0 +1,22 @@
#!/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()

293
keepalived/icon.svg Normal file
View File

@ -0,0 +1,293 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg6517"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="icon.svg"
viewBox="0 0 96 96">
<defs
id="defs6519">
<linearGradient
id="Background">
<stop
id="stop4178"
offset="0"
style="stop-color:#b8b8b8;stop-opacity:1" />
<stop
id="stop4180"
offset="1"
style="stop-color:#c9c9c9;stop-opacity:1" />
</linearGradient>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Inner Shadow"
id="filter1121">
<feFlood
flood-opacity="0.59999999999999998"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1123" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="out"
result="composite1"
id="feComposite1125" />
<feGaussianBlur
in="composite1"
stdDeviation="1"
result="blur"
id="feGaussianBlur1127" />
<feOffset
dx="0"
dy="2"
result="offset"
id="feOffset1129" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="atop"
result="composite2"
id="feComposite1131" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter950">
<feFlood
flood-opacity="0.25"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood952" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite954" />
<feGaussianBlur
in="composite1"
stdDeviation="1"
result="blur"
id="feGaussianBlur956" />
<feOffset
dx="0"
dy="1"
result="offset"
id="feOffset958" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite960" />
<feBlend
blend="normal"
id="feBlend3895"
in2="composite2" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath873">
<g
transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
id="g875"
inkscape:label="Layer 1"
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
<path
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
id="path877"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssss" />
</g>
</clipPath>
<filter
inkscape:collect="always"
id="filter891"
inkscape:label="Badge Shadow">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.71999962"
id="feGaussianBlur893" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8.1490724"
inkscape:cx="49.021381"
inkscape:cy="46.975739"
inkscape:document-units="px"
inkscape:current-layer="layer3"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1056"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
showborder="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="false">
<inkscape:grid
type="xygrid"
id="grid821" />
<sodipodi:guide
orientation="1,0"
position="16,48"
id="guide823" />
<sodipodi:guide
orientation="0,1"
position="64,80"
id="guide825" />
<sodipodi:guide
orientation="1,0"
position="80,40"
id="guide827" />
<sodipodi:guide
orientation="0,1"
position="64,16"
id="guide829" />
</sodipodi:namedview>
<metadata
id="metadata6522">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="BACKGROUND"
inkscape:groupmode="layer"
id="layer1"
transform="translate(268,-635.29076)"
style="display:inline">
<path
style="fill:#333333;fill-opacity:0.93333334000000001;stroke:none;display:inline;filter:url(#filter1121);opacity:0.7"
d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
id="path6455"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssss" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="PLACEHOLDER LETTER"
style="display:inline">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter950)"
x="17.458124"
y="69.1772"
id="text3891"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3893"
x="17.458124"
y="69.1772"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:56px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Medium';fill:#ffffff;fill-opacity:1">ka</tspan></text>
<rect
style="opacity:0.7;fill:none;stroke:none"
id="rect3021"
width="64"
height="64"
x="16"
y="15.449201" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="BADGE"
style="display:none"
sodipodi:insensitive="true">
<g
style="display:inline"
transform="translate(-340.00001,-581)"
id="g4394"
clip-path="none">
<g
id="g855">
<g
inkscape:groupmode="maskhelper"
id="g870"
clip-path="url(#clipPath873)"
style="opacity:0.6;filter:url(#filter891)">
<path
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
sodipodi:ry="12"
sodipodi:rx="12"
sodipodi:cy="552.36218"
sodipodi:cx="252"
id="path844"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
</g>
<g
id="g862">
<path
sodipodi:type="arc"
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path4398"
sodipodi:cx="252"
sodipodi:cy="552.36218"
sodipodi:rx="12"
sodipodi:ry="12"
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
<path
transform="matrix(1.25,0,0,1.25,33,-100.45273)"
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
sodipodi:ry="12"
sodipodi:rx="12"
sodipodi:cy="552.36218"
sodipodi:cx="252"
id="path4400"
style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
<path
sodipodi:type="star"
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path4459"
sodipodi:sides="5"
sodipodi:cx="666.19574"
sodipodi:cy="589.50385"
sodipodi:r1="7.2431178"
sodipodi:r2="4.3458705"
sodipodi:arg1="1.0471976"
sodipodi:arg2="1.6755161"
inkscape:flatsided="false"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

19
keepalived/layer.yaml Normal file
View File

@ -0,0 +1,19 @@
"includes":
- "layer:options"
- "layer:basic"
- "interface:juju-info"
- "interface:public-address"
- "interface:http"
- "layer:status"
"exclude": [".travis.yml", "tests", "tox.ini", "test-requirements.txt", "unit_tests"]
"options":
"basic":
"use_venv": !!bool "true"
"packages": []
"python_packages": []
"include_system_packages": !!bool "false"
"status":
"patch-hookenv": !!bool "true"
"keepalived": {}
"repo": "https://github.com/juju-solutions/charm-keepalived.git"
"is": "keepalived"

View File

@ -0,0 +1,60 @@
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

View File

@ -0,0 +1,446 @@
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)
pkgs = _load_wheelhouse_versions().keys() - set(pre_install_pkgs)
reinstall_flag = '--force-reinstall'
if not cfg.get('use_venv', True) and pre_eoan:
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 _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()

View File

@ -0,0 +1,114 @@
# 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)

View File

@ -0,0 +1,26 @@
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

View File

@ -0,0 +1,189 @@
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

20
keepalived/make_docs Normal file
View File

@ -0,0 +1,20 @@
#!.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')

26
keepalived/metadata.yaml Normal file
View File

@ -0,0 +1,26 @@
"name": "keepalived"
"summary": "Failover and monitoring daemon for LVS clusters"
"maintainers":
- "Konstantinos Tsakalozos <kos.tsakalozos@canonical.com>"
- "Valentin Boucher <valentin.boucher@kontron.com>"
"description": |
keepalived is used for monitoring real servers within a Linux Virtual
Server (LVS) cluster.
"tags":
- "networking"
"series":
- "focal"
- "bionic"
- "xenial"
"requires":
"juju-info":
"scope": "container"
"interface": "juju-info"
"lb-sink":
"interface": "http"
"provides":
"loadbalancer":
"interface": "public-address"
"website":
"interface": "http"
"subordinate": !!bool "true"

16
keepalived/pydocmd.yml Normal file
View File

@ -0,0 +1,16 @@
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

View File

View File

@ -0,0 +1,124 @@
import os
import re
from subprocess import check_output
from charms.reactive import set_flag, when, when_not, hook
from charms.reactive.flags import clear_flag
from charmhelpers.core.templating import render
from charmhelpers.fetch import apt_update, apt_install
from charmhelpers.core.hookenv import config, is_leader
from charmhelpers.core.host import service_restart
from charmhelpers.core.host import service_pause, service_resume
from charms.layer import status
SYSCTL_FILE = os.path.join(os.sep, 'etc', 'sysctl.d', '50-keepalived.conf')
KEEPALIVED_CONFIG_FILE = os.path.join(os.sep, 'etc', 'keepalived',
'keepalived.conf')
@when_not('keepalived.package.installed')
def install_keepalived_package():
''' Install keepalived package '''
status.maintenance('Installing keepalived')
apt_update(fatal=True)
apt_install(['keepalived', 'net-tools'], fatal=True)
set_flag('keepalived.package.installed')
def default_route_interface():
''' Returns the network interface of the system's default route '''
default_interface = None
cmd = ['route']
output = check_output(cmd).decode('utf8')
for line in output.split('\n'):
if 'default' in line:
default_interface = line.split(' ')[-1]
return default_interface
@when('keepalived.package.installed')
@when_not('keepalived.started')
@when_not('upgrade.series.in-progress')
def configure_keepalived_service():
''' Set up the keepalived service '''
virtual_ip = config().get('virtual_ip')
if virtual_ip == "":
status.blocked('Please configure virtual ips')
return
network_interface = config().get('network_interface')
if network_interface == "":
network_interface = default_route_interface()
context = {'is_leader': is_leader(),
'virtual_ip': virtual_ip,
'network_interface': network_interface,
'router_id': config().get('router_id'),
'service_port': config().get('port'),
'healthcheck_interval': config().get('healthcheck_interval'),
}
render(source='keepalived.conf',
target=KEEPALIVED_CONFIG_FILE,
context=context,
perms=0o644)
service_restart('keepalived')
render(source='50-keepalived.conf',
target=SYSCTL_FILE,
context={'sysctl': {'net.ipv4.ip_nonlocal_bind': 1}},
perms=0o644)
service_restart('procps')
status.active('VIP ready')
set_flag('keepalived.started')
@when('config.changed')
def reconfigure():
clear_flag('keepalived.started')
@when('website.available', 'keepalived.started')
def website_available(website):
ipaddr = re.split('/', config()['virtual_ip'])[0]
vip_hostname = config()['vip_hostname']
hostname = vip_hostname if vip_hostname else ipaddr
# a port to export over a relation
# TODO: this could be more tightly coupled with the actual
# service via a relation
port = config()['port']
website.configure(port=port, private_address=ipaddr, hostname=hostname)
@when('loadbalancer.available', 'keepalived.started')
def loadbalancer_available(loadbalancer):
''' Send the virtual IP '''
ipaddr = re.split('/', config()['virtual_ip'])[0]
port = config()['port']
loadbalancer.set_address_port(ipaddr, port)
@hook('upgrade-charm')
def upgrade_charm():
clear_flag('keepalived.started')
@hook('pre-series-upgrade')
def pre_series_upgrade():
service_pause('keepalived')
service_pause('procps')
status.blocked('Series upgrade in progress')
@hook('post-series-upgrade')
def post_series_upgrade():
service_resume('keepalived')
service_resume('procps')
clear_flag('keepalived.started')

View File

@ -0,0 +1,4 @@
from charms import layer
layer.status._initialize()

View File

@ -0,0 +1,3 @@
mock
flake8
pytest

1
keepalived/revision Normal file
View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1,3 @@
{% for key in sysctl %}
{{ key }}={{ sysctl[key] }}
{% endfor %}

View File

@ -0,0 +1,22 @@
vrrp_script chk_svc_port {
# returns 1 if connection is refused
script "/bin/bash -c '</dev/tcp/127.0.0.1/{{ service_port }}'"
# check every 2 seconds
interval {{ healthcheck_interval }}
# make sure master priority drops below backup priority on failure
weight 2
}
vrrp_instance VI_1 {
interface {{ network_interface }}
state {% if is_leader %}MASTER{% else %}BACKUP{% endif %}
priority {% if is_leader %}101{% else %}100{% endif %}
virtual_router_id {{ router_id }}
virtual_ipaddress {
{{ virtual_ip }}
}
track_script {
chk_svc_port
}
}

14
keepalived/tox.ini Normal file
View File

@ -0,0 +1,14 @@
[tox]
envlist = py3
skipsdist = true
[testenv]
basepython=python3
envdir={toxworkdir}/py3
deps=
pytest
charms.reactive
pydoc-markdown
[testenv:docs]
commands=python make_docs

1
keepalived/version Normal file
View File

@ -0,0 +1 @@
0ea81f0c

18
keepalived/wheelhouse.txt Normal file
View File

@ -0,0 +1,18 @@
# 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
# pin Jinja2, PyYAML and MarkupSafe to the last versions supporting python 3.5
# for trusty
Jinja2<=2.10.1
PyYAML<=5.2
MarkupSafe<2.0.0
setuptools<42
setuptools-scm<=1.17.0
charmhelpers>=0.4.0,<1.0.0
charms.reactive>=0.1.0,<2.0.0
wheel<0.34
# pin netaddr to avoid pulling importlib-resources
netaddr<=0.7.19

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More