diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..722d5e71d93ca0aa0db6fd22452e46be5604a84d --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d8c2f75a112fe19aa09556901363a00b36ba3f24 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/README.md @@ -0,0 +1,21 @@ +# OAI Operators + +## Prepare environment + +```bash +juju add-model oai-01 +``` + +## Deployment + +Build charms: + +```bash +./build.sh +``` + +Deploy bundle: + +```bash +juju deploy ./bundle.yaml --trust +``` diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/build.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..74bfeef1160c069cf448a9461cdbbf6158390877 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +function build() { + charm=$1 + cd oai-$charm-operator/ + #charmcraft clean + #charmcraft build + mv oai-${charm}_ubuntu-20.04-amd64.charm $charm.charm + cd .. +} + +charms="nrf amf smf spgwu-tiny db gnb nr-ue" +for charm in $charms; do + build $charm & +done + +wait diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/bundle.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/bundle.yaml new file mode 100644 index 0000000000000000000000000000000000000000..152ef999e210b8034feac2faf52e2f81e654fda4 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/bundle.yaml @@ -0,0 +1,76 @@ +bundle: kubernetes +applications: + nrf: + charm: ./oai-nrf-operator/nrf.charm + scale: 1 + trust: true + options: + start-tcpdump: false + resources: + oai-nrf-image: rdefosseoai/oai-nrf:v1.1.0 + tcpdump-image: corfr/tcpdump:latest + amf: + charm: ./oai-amf-operator/amf.charm + trust: true + options: + start-tcpdump: false + resources: + oai-amf-image: rdefosseoai/oai-amf:v1.1.0 + tcpdump-image: corfr/tcpdump:latest + scale: 1 + smf: + charm: ./oai-smf-operator/smf.charm + scale: 1 + trust: true + options: + start-tcpdump: false + resources: + oai-smf-image: rdefosseoai/oai-smf:develop + tcpdump-image: corfr/tcpdump:latest + spgwu-tiny: + charm: ./oai-spgwu-tiny-operator/spgwu-tiny.charm + scale: 1 + trust: true + options: + start-tcpdump: false + resources: + oai-spgwu-tiny-image: rdefosseoai/oai-spgwu-tiny:v1.1.2 + tcpdump-image: corfr/tcpdump:latest + db: + charm: ./oai-db-operator/db.charm + scale: 1 + resources: + oai-db-image: mysql:5.5 + gnb: + charm: ./oai-gnb-operator/gnb.charm + scale: 1 + trust: true + resources: + oai-gnb-image: rdefosseoai/oai-gnb:develop + tcpdump-image: corfr/tcpdump:latest + nr-ue: + charm: ./oai-nr-ue-operator/nr-ue.charm + scale: 1 + trust: true + resources: + oai-nr-ue-image: rdefosseoai/oai-nr-ue:develop + tcpdump-image: corfr/tcpdump:latest +relations: + - - db:db + - amf:db + - - nrf:nrf + - amf:nrf + - - nrf:nrf + - smf:nrf + - - smf:amf + - amf:amf + - - nrf:nrf + - spgwu-tiny:nrf + - - spgwu-tiny:smf + - smf:smf + - - gnb:amf + - amf:amf + - - nr-ue:gnb + - gnb:gnb + - - spgwu-tiny:spgwu + - gnb:spgwu diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/deploy.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..2fc4b779aac65492a26951bd9f8f3537a221ec1c --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/deploy.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +juju deploy ./bundle.yaml --trust \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c7162b91b605b822f52866f7d1674fcff141022f --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/README.md @@ -0,0 +1 @@ +# oai-amf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7700bbe4ebc19ea95913fcc75d48b65c82cd9d35 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/actions.yaml @@ -0,0 +1,14 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + +starttcpdump: + description: "Start Tcpdump Action" + +stoptcpdump: + description: "Stop Tcpdump Action" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..339ad653d93ead9f3b8ad8508fef5f3ff1dd9c94 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/metadata.yaml @@ -0,0 +1,33 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-amf +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + amf: + resource: oai-amf-image + tcpdump: + resource: tcpdump-image +resources: + oai-amf-image: + type: oci-image + description: OCI image for oai-amf (rdefosseoai/oai-amf:v1.1.0) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +requires: + nrf: + interface: nrf + db: + interface: mysql +provides: + amf: + interface: amf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..919db3fc2bf099ab5c589fba63b8dbf8d2e7dfa4 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/charm.py @@ -0,0 +1,434 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +from typing import Optional +import time + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ConnectionError + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiAmfCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.amf_pebble_ready: self._on_oai_amf_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.amf_relation_joined: self._provide_service_info, + self.on.nrf_relation_changed: self._update_service, + self.on.nrf_relation_broken: self._update_service, + self.on.db_relation_changed: self._update_service, + self.on.db_relation_broken: self._update_service, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + nrf_host=None, + nrf_port=None, + db_host=None, + db_port=None, + db_user=None, + db_password=None, + db_database=None, + nrf_api_version=None, + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + # Action hooks + self.framework.observe(self.on.starttcpdump_action, self._on_starttcpdump_action) + self.framework.observe(self.on.stoptcpdump_action, self._on_stoptcpdump_action) + + #################################### + # Custom Actions + #################################### + + def _on_starttcpdump_action(self, event): + try: + self._start_service("tcpdump", "tcpdump") + event.set_results({ + "output": f"tcpdump started successfully" + }) + except Exception as e: + event.fail(f"Start tcpdump action failed with the following exception: {e}") + + def _on_stoptcpdump_action(self, event): + try: + self._stop_service("tcpdump", "tcpdump") + event.set_results({ + "output": f"tcpdump stoped successfully" + }) + except Exception as e: + event.fail(f"Stop tcpdump action failed with the following exception: {e}") + + #################################### + # Observers - Relation Events + #################################### + + def _provide_service_info(self, event): + if self.unit.is_leader() and self.is_service_running: + pod_ip = self.pod_ip + if not pod_ip: + logger.info("Not pod IP found") + event.defer() + else: + for relation in self.framework.model.relations["amf"]: + logger.info(f"Found relation {relation.name} with id {relation.id}") + relation.data[self.app]["host"] = self.app.name + relation.data[self.app]["ip-address"] = str(pod_ip) + relation.data[self.app]["port"] = str(HTTP1_PORT) + relation.data[self.app]["api-version"] = "v1" + else: + logger.info("not relations found") + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("oai-amf", 38412, 38412, "SCTP"), + ("http1", 80, 80, "TCP"), + ("http2", 9090, 9090, "TCP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_amf_pebble_ready(self, event): + container = event.workload + entrypoint = "/bin/bash /openair-amf/bin/entrypoint.sh" + command = " ".join( + ["/openair-amf/bin/oai_amf", "-c", "/openair-amf/etc/amf.conf", "-o"] + ) + pebble_layer = { + "summary": "oai_amf layer", + "description": "pebble config layer for oai_amf", + "services": { + "oai_amf": { + "override": "replace", + "summary": "oai_amf", + "command": f"{entrypoint} {command}", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + "INSTANCE": "0", + "PID_DIRECTORY": "/var/run", + "MCC": "208", + "MNC": "95", + "REGION_ID": "128", + "AMF_SET_ID": "1", + "SERVED_GUAMI_MCC_0": "208", + "SERVED_GUAMI_MNC_0": "95", + "SERVED_GUAMI_REGION_ID_0": "128", + "SERVED_GUAMI_AMF_SET_ID_0": "1", + "SERVED_GUAMI_MCC_1": "460", + "SERVED_GUAMI_MNC_1": "11", + "SERVED_GUAMI_REGION_ID_1": "10", + "SERVED_GUAMI_AMF_SET_ID_1": "1", + "PLMN_SUPPORT_MCC": "208", + "PLMN_SUPPORT_MNC": "95", + "PLMN_SUPPORT_TAC": "0x0001", + "SST_0": "1", + "SD_0": "1", + "SST_1": "111", + "SD_1": "124", + "AMF_INTERFACE_NAME_FOR_NGAP": "eth0", + "AMF_INTERFACE_NAME_FOR_N11": "eth0", + "SMF_INSTANCE_ID_0": "1", + "SMF_IPV4_ADDR_0": "0.0.0.0", + "SMF_HTTP_VERSION_0": "v1", + "SMF_FQDN_0": "localhost", + "SMF_INSTANCE_ID_1": "2", + "SMF_IPV4_ADDR_1": "0.0.0.0", + "SMF_HTTP_VERSION_1": "v1", + "SMF_FQDN_1": "localhost", + "AUSF_IPV4_ADDRESS": "127.0.0.1", + "AUSF_PORT": 80, + "AUSF_API_VERSION": "v1", + "NF_REGISTRATION": "yes", + "SMF_SELECTION": "yes", + "USE_FQDN_DNS": "yes", + "OPERATOR_KEY": "63bfa50ee6523365ff14c1f45f88737d", + }, + } + }, + } + try: + container.add_layer("oai_amf", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def is_nrf_ready(self): + return ( + self._stored.nrf_host + and self._stored.nrf_port + and self._stored.nrf_api_version + ) + + @property + def is_db_ready(self): + return ( + self._stored.db_host + and self._stored.db_port + and self._stored.db_user + and self._stored.db_password + and self._stored.db_database + ) + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + return IPv4Address( + check_output(["unit-get", "private-address"]).decode().strip() + ) + + @property + def container_name(self): + return "amf" + + @property + def service_name(self): + return "oai_amf" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._load_nrf_data() + self._load_db_data() + if self.is_nrf_ready and self.is_db_ready: + try: + self._configure_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self._start_service(container_name="amf", service_name="oai_amf"): + self._provide_service_info(event) + self.unit.status = ActiveStatus() + else: + self._stop_service(container_name="amf", service_name="oai_amf") + self.unit.status = BlockedStatus("need nrf and db relations") + + def _load_nrf_data(self): + relation = self.framework.model.get_relation("nrf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.nrf_host = relation_data.get("host") + self._stored.nrf_port = relation_data.get("port") + self._stored.nrf_api_version = relation_data.get("api-version") + else: + self._stored.nrf_host = None + self._stored.nrf_port = None + self._stored.nrf_api_version = None + + def _load_db_data(self): + relation = self.framework.model.get_relation("db") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.db_host = relation_data.get("host") + self._stored.db_port = relation_data.get("port") + self._stored.db_user = relation_data.get("user") + self._stored.db_password = relation_data.get("password") + self._stored.db_database = relation_data.get("database") + else: + self._stored.db_host = None + self._stored.db_port = None + self._stored.db_user = None + self._stored.db_password = None + self._stored.db_database = None + + def _configure_service(self): + container = self.unit.get_container("amf") + if self.service_name in container.get_plan().services: + container.add_layer( + "oai_amf", + { + "services": { + "oai_amf": { + "override": "merge", + "environment": { + "NRF_FQDN": self._stored.nrf_host, + "NRF_IPV4_ADDRESS": "0.0.0.0", + "NRF_PORT": self._stored.nrf_port, + "NRF_API_VERSION": self._stored.nrf_api_version, + "MYSQL_SERVER": f"{self._stored.db_host}:{self._stored.db_port}", + "MYSQL_USER": self._stored.db_user, + "MYSQL_PASS": self._stored.db_password, + "MYSQL_DB": self._stored.db_database, + }, + } + }, + }, + combine=True, + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = container.get_service(service_name).is_running() + + if service_exists and not is_running: + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + #################################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiAmfCharm, use_juju_for_storage=True) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-amf-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d323041b2999a75953309c06ed3977674744599e --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/README.md @@ -0,0 +1 @@ +# oai-db \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d5138d0fb05debbf532649f825272e4ed9ed142 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/actions.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bfefb04622a052e9e21cd696af0cb8d7f53f4f1 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/config.yaml @@ -0,0 +1,10 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: {} \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..874acc4e9e133f4ce21583d8aeb4128ba51dd9b8 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/metadata.yaml @@ -0,0 +1,23 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-db +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + db: + resource: oai-db-image +resources: + oai-db-image: + type: oci-image + description: OCI image for oai-nrf (davigar15/oai-test-db:latest) +provides: + db: + interface: mysql diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..2fec24ec6c058535d92b2c87146abee615da4735 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/charm.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + +from ipaddress import IPv4Address +import logging +from pathlib import Path +from subprocess import check_output +from typing import Optional + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ConnectionError + + +logger = logging.getLogger(__name__) + +MYSQL_PORT = 3306 + + +class OaiDbCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.db_pebble_ready: self._on_oai_db_pebble_ready, + self.on.db_relation_joined: self._provide_service_info, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + + #################################### + # Observers - Relation Events + #################################### + + def _provide_service_info(self, event): + if not self.unit.is_leader(): + return + pod_ip = self.pod_ip + if pod_ip: + event.relation.data[self.app]["host"] = str(pod_ip) + event.relation.data[self.app]["port"] = str(MYSQL_PORT) + event.relation.data[self.app]["user"] = "root" + event.relation.data[self.app]["password"] = "root" + event.relation.data[self.app]["database"] = "oai_db" + else: + event.defer() + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_db_pebble_ready(self, event): + container = event.workload + pebble_layer = { + "summary": "oai_db layer", + "description": "pebble config layer for oai_db", + "services": { + "oai_db": { + "override": "replace", + "summary": "oai_db", + "command": "docker-entrypoint.sh mysqld", + "environment": { + "MYSQL_ROOT_PASSWORD": "root", + "MYSQL_DATABASE": "oai_db", + "GOSU_VERSION": "1.13", + "MARIADB_MAJOR": "10.3", + "MARIADB_VERSION": "1:10.3.31+maria~focal", + }, + } + }, + } + try: + container.add_layer("oai_db", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + #################################### + # Properties + #################################### + + @property + def pod_ip(self) -> Optional[IPv4Address]: + return IPv4Address( + check_output(["unit-get", "private-address"]).decode().strip() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._initialize_db() + self._start_service(container_name="db", service_name="oai_db") + self.unit.status = ActiveStatus() + + def _initialize_db(self): + container = self.unit.get_container("db") + container.push( + "/docker-entrypoint-initdb.d/db.sql", Path("templates/db.sql").read_text() + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = container.get_service(service_name).is_running() + + if service_exists and not is_running: + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + +if __name__ == "__main__": + main(OaiDbCharm, use_juju_for_storage=True) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/templates/db.sql b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/templates/db.sql new file mode 100644 index 0000000000000000000000000000000000000000..c8a0e435059b47e0f8f9da35e83ed0693d3b76e6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/templates/db.sql @@ -0,0 +1,213 @@ +-- MySQL dump 10.13 Distrib 5.5.46, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: oai_db +-- ------------------------------------------------------ +-- Server version 5.5.46-0ubuntu0.14.04.2 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `apn` +-- + +DROP TABLE IF EXISTS `apn`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `apn` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `apn-name` varchar(60) NOT NULL, + `pdn-type` enum('IPv4','IPv6','IPv4v6','IPv4_or_IPv6') NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `apn-name` (`apn-name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `apn` +-- + +LOCK TABLES `apn` WRITE; +/*!40000 ALTER TABLE `apn` DISABLE KEYS */; +/*!40000 ALTER TABLE `apn` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `mmeidentity` +-- + +DROP TABLE IF EXISTS `mmeidentity`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `mmeidentity` ( + `idmmeidentity` int(11) NOT NULL AUTO_INCREMENT, + `mmehost` varchar(255) DEFAULT NULL, + `mmerealm` varchar(200) DEFAULT NULL, + `UE-Reachability` tinyint(1) NOT NULL COMMENT 'Indicates whether the MME supports UE Reachability Notifcation', + PRIMARY KEY (`idmmeidentity`) +) ENGINE=MyISAM AUTO_INCREMENT=46 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `mmeidentity` +-- + +LOCK TABLES `mmeidentity` WRITE; +/*!40000 ALTER TABLE `mmeidentity` DISABLE KEYS */; +INSERT INTO `mmeidentity` VALUES (2,'mme2.openair4G.eur','openair4G.eur',0),(1,'nano.openair4G.eur','openair4G.eur',0),(5,'abeille.openair4G.eur','openair4G.eur',0),(4,'yang.openair4G.eur','openair4G.eur',0),(3,'mme3.openair4G.eur','openair4G.eur',0),(6,'calisson.openair4G.eur','openair4G.eur',0); +/*!40000 ALTER TABLE `mmeidentity` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `pdn` +-- + +DROP TABLE IF EXISTS `pdn`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `pdn` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `apn` varchar(60) NOT NULL, + `pdn_type` enum('IPv4','IPv6','IPv4v6','IPv4_or_IPv6') NOT NULL DEFAULT 'IPv4', + `pdn_ipv4` varchar(15) DEFAULT '0.0.0.0', + `pdn_ipv6` varchar(45) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT '0:0:0:0:0:0:0:0', + `aggregate_ambr_ul` int(10) unsigned DEFAULT '50000000', + `aggregate_ambr_dl` int(10) unsigned DEFAULT '100000000', + `pgw_id` int(11) NOT NULL, + `users_imsi` varchar(15) NOT NULL, + `qci` tinyint(3) unsigned NOT NULL DEFAULT '9', + `priority_level` tinyint(3) unsigned NOT NULL DEFAULT '15', + `pre_emp_cap` enum('ENABLED','DISABLED') DEFAULT 'DISABLED', + `pre_emp_vul` enum('ENABLED','DISABLED') DEFAULT 'DISABLED', + `LIPA-Permissions` enum('LIPA-prohibited','LIPA-only','LIPA-conditional') NOT NULL DEFAULT 'LIPA-only', + PRIMARY KEY (`id`,`pgw_id`,`users_imsi`), + KEY `fk_pdn_pgw1_idx` (`pgw_id`), + KEY `fk_pdn_users1_idx` (`users_imsi`) +) ENGINE=MyISAM AUTO_INCREMENT=60 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `pdn` +-- + +LOCK TABLES `pdn` WRITE; +/*!40000 ALTER TABLE `pdn` DISABLE KEYS */; +INSERT INTO `pdn` VALUES (1,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000001',9,15,'DISABLED','ENABLED','LIPA-only'),(41,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'20834123456789',9,15,'DISABLED','ENABLED','LIPA-only'),(40,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'20810000001234',9,15,'DISABLED','ENABLED','LIPA-only'),(42,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'31002890832150',9,15,'DISABLED','ENABLED','LIPA-only'),(16,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000002',9,15,'DISABLED','ENABLED','LIPA-only'),(43,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'001010123456789',9,15,'DISABLED','ENABLED','LIPA-only'),(2,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000002',9,15,'DISABLED','ENABLED','LIPA-only'),(3,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000003',9,15,'DISABLED','ENABLED','LIPA-only'),(4,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000004',9,15,'DISABLED','ENABLED','LIPA-only'),(5,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000005',9,15,'DISABLED','ENABLED','LIPA-only'),(6,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000006',9,15,'DISABLED','ENABLED','LIPA-only'),(7,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930000000007',9,15,'DISABLED','ENABLED','LIPA-only'),(8,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000001',9,15,'DISABLED','ENABLED','LIPA-only'),(9,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000002',9,15,'DISABLED','ENABLED','LIPA-only'),(10,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000003',9,15,'DISABLED','ENABLED','LIPA-only'),(11,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000004',9,15,'DISABLED','ENABLED','LIPA-only'),(12,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000005',9,15,'DISABLED','ENABLED','LIPA-only'),(13,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000006',9,15,'DISABLED','ENABLED','LIPA-only'),(14,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208940000000007',9,15,'DISABLED','ENABLED','LIPA-only'),(15,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000001',9,15,'DISABLED','ENABLED','LIPA-only'),(17,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000003',9,15,'DISABLED','ENABLED','LIPA-only'),(18,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000004',9,15,'DISABLED','ENABLED','LIPA-only'),(19,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000005',9,15,'DISABLED','ENABLED','LIPA-only'),(20,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000006',9,15,'DISABLED','ENABLED','LIPA-only'),(21,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000007',9,15,'DISABLED','ENABLED','LIPA-only'),(22,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001100',9,15,'DISABLED','ENABLED','LIPA-only'),(23,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001101',9,15,'DISABLED','ENABLED','LIPA-only'),(24,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001102',9,15,'DISABLED','ENABLED','LIPA-only'),(25,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001103',9,15,'DISABLED','ENABLED','LIPA-only'),(26,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001104',9,15,'DISABLED','ENABLED','LIPA-only'),(27,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001105',9,15,'DISABLED','ENABLED','LIPA-only'),(28,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001106',9,15,'DISABLED','ENABLED','LIPA-only'),(29,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001107',9,15,'DISABLED','ENABLED','LIPA-only'),(30,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001108',9,15,'DISABLED','ENABLED','LIPA-only'),(31,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001109',9,15,'DISABLED','ENABLED','LIPA-only'),(32,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001110',9,15,'DISABLED','ENABLED','LIPA-only'),(33,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001111',9,15,'DISABLED','ENABLED','LIPA-only'),(34,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001112',9,15,'DISABLED','ENABLED','LIPA-only'),(35,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001113',9,15,'DISABLED','ENABLED','LIPA-only'),(44,'operator','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001113',9,15,'DISABLED','ENABLED','LIPA-only'),(45,'operator','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001112',9,15,'DISABLED','ENABLED','LIPA-only'),(46,'operator','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208930100001111',9,15,'DISABLED','ENABLED','LIPA-only'),(47,'operator','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000002',9,15,'DISABLED','ENABLED','LIPA-only'),(48,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000008',9,15,'DISABLED','ENABLED','LIPA-only'),(49,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000009',9,15,'DISABLED','ENABLED','LIPA-only'),(50,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000010',9,15,'DISABLED','ENABLED','LIPA-only'),(51,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000011',9,15,'DISABLED','ENABLED','LIPA-only'),(52,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000012',9,15,'DISABLED','ENABLED','LIPA-only'),(53,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000013',9,15,'DISABLED','ENABLED','LIPA-only'),(54,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000014',9,15,'DISABLED','ENABLED','LIPA-only'),(55,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208950000000015',9,15,'DISABLED','ENABLED','LIPA-only'),(56,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001118',9,15,'DISABLED','ENABLED','LIPA-only'),(57,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001121',9,15,'DISABLED','ENABLED','LIPA-only'),(58,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001120',9,15,'DISABLED','ENABLED','LIPA-only'),(59,'oai.ipv4','IPv4','0.0.0.0','0:0:0:0:0:0:0:0',50000000,100000000,3,'208920100001119',9,15,'DISABLED','ENABLED','LIPA-only'); +/*!40000 ALTER TABLE `pdn` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `pgw` +-- + +DROP TABLE IF EXISTS `pgw`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `pgw` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ipv4` varchar(15) NOT NULL, + `ipv6` varchar(39) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `ipv4` (`ipv4`), + UNIQUE KEY `ipv6` (`ipv6`) +) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `pgw` +-- + +LOCK TABLES `pgw` WRITE; +/*!40000 ALTER TABLE `pgw` DISABLE KEYS */; +INSERT INTO `pgw` VALUES (1,'127.0.0.1','0:0:0:0:0:0:0:1'),(2,'192.168.56.101',''),(3,'10.0.0.2','0'); +/*!40000 ALTER TABLE `pgw` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `terminal-info` +-- + +DROP TABLE IF EXISTS `terminal-info`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `terminal-info` ( + `imei` varchar(15) NOT NULL, + `sv` varchar(2) NOT NULL, + UNIQUE KEY `imei` (`imei`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `terminal-info` +-- + +LOCK TABLES `terminal-info` WRITE; +/*!40000 ALTER TABLE `terminal-info` DISABLE KEYS */; +/*!40000 ALTER TABLE `terminal-info` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `imsi` varchar(15) NOT NULL COMMENT 'IMSI is the main reference key.', + `msisdn` varchar(46) DEFAULT NULL COMMENT 'The basic MSISDN of the UE (Presence of MSISDN is optional).', + `imei` varchar(15) DEFAULT NULL COMMENT 'International Mobile Equipment Identity', + `imei_sv` varchar(2) DEFAULT NULL COMMENT 'International Mobile Equipment Identity Software Version Number', + `ms_ps_status` enum('PURGED','NOT_PURGED') DEFAULT 'PURGED' COMMENT 'Indicates that ESM and EMM status are purged from MME', + `rau_tau_timer` int(10) unsigned DEFAULT '120', + `ue_ambr_ul` bigint(20) unsigned DEFAULT '50000000' COMMENT 'The Maximum Aggregated uplink MBRs to be shared across all Non-GBR bearers according to the subscription of the user.', + `ue_ambr_dl` bigint(20) unsigned DEFAULT '100000000' COMMENT 'The Maximum Aggregated downlink MBRs to be shared across all Non-GBR bearers according to the subscription of the user.', + `access_restriction` int(10) unsigned DEFAULT '60' COMMENT 'Indicates the access restriction subscription information. 3GPP TS.29272 #7.3.31', + `mme_cap` int(10) unsigned zerofill DEFAULT NULL COMMENT 'Indicates the capabilities of the MME with respect to core functionality e.g. regional access restrictions.', + `mmeidentity_idmmeidentity` int(11) NOT NULL DEFAULT '0', + `key` varbinary(16) NOT NULL DEFAULT '0' COMMENT 'UE security key', + `RFSP-Index` smallint(5) unsigned NOT NULL DEFAULT '1' COMMENT 'An index to specific RRM configuration in the E-UTRAN. Possible values from 1 to 256', + `urrp_mme` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'UE Reachability Request Parameter indicating that UE activity notification from MME has been requested by the HSS.', + `sqn` bigint(20) unsigned zerofill NOT NULL, + `rand` varbinary(16) NOT NULL, + `OPc` varbinary(16) DEFAULT NULL COMMENT 'Can be computed by HSS', + PRIMARY KEY (`imsi`,`mmeidentity_idmmeidentity`), + KEY `fk_users_mmeidentity_idx1` (`mmeidentity_idmmeidentity`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `users` +-- + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; +INSERT INTO `users` VALUES ('208950000000030','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); +INSERT INTO `users` VALUES ('208950000000031','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); +INSERT INTO `users` VALUES ('208950000000032','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); +INSERT INTO `users` VALUES ('208950000000033','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); +INSERT INTO `users` VALUES ('208950000000034','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); +-- +-- Add a new user entry here using use above insert statement as template +-- +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2016-06-28 11:41:40 \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-db-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a710fea1fc6cd5d3e28d656706989968bdf2ab10 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/README.md @@ -0,0 +1,24 @@ +# oai-gnb + +## Description + +TODO: Describe your charm in a few paragraphs of Markdown + +## Usage + +TODO: Provide high-level usage, such as required config or relations + + +## Relations + +TODO: Provide any relations which are provided or required by your charm + +## OCI Images + +TODO: Include a link to the default image your charm uses + +## Contributing + +Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines +on enhancements to this charm following best practice guidelines, and +`CONTRIBUTING.md` for developer guidance. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d5138d0fb05debbf532649f825272e4ed9ed142 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/actions.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bd209c0e7deb8f605d58dd46e995f58d3596b6ae --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/metadata.yaml @@ -0,0 +1,36 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-gnb +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + gnb: + resource: oai-gnb-image + tcpdump: + resource: tcpdump-image + +resources: + oai-gnb-image: + type: oci-image + description: OCI image for oai-gnb (rdefosseoai/oai-gnb:develop) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +requires: + amf: + interface: amf + limit: 1 + spgwu: + interface: spgwu + limit: 1 +provides: + gnb: + interface: gnb diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..fe8ec8a227acea500c440fa1a68396e69fe3ee78 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/charm.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +from typing import Optional +import time + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus +from ops.pebble import ConnectionError + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiGnbCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.gnb_pebble_ready: self._on_oai_gnb_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.gnb_relation_joined: self._provide_service_info, + self.on.amf_relation_changed: self._update_service, + self.on.amf_relation_broken: self._update_service, + self.on.spgwu_relation_changed: self._update_service, + self.on.spgwu_relation_broken: self._update_service, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + amf_host=None, + amf_port=None, + amf_api_version=None, + spgwu_ready=False, + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + + #################################### + # Observers - Relation Events + #################################### + + def _provide_service_info(self, event): + if self.unit.is_leader() and self.is_service_running: + pod_ip = self.pod_ip + if pod_ip: + for relation in self.framework.model.relations["gnb"]: + logger.info(f"Found relation {relation.name} with id {relation.id}") + relation.data[self.app]["host"] = str(pod_ip) + else: + logger.info("not relations found") + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("s1c", 36412, 36412, "UDP"), + ("s1u", 2152, 2152, "UDP"), + ("x2c", 36422, 36422, "UDP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_gnb_pebble_ready(self, event): + pod_ip = self.pod_ip + if not pod_ip: + event.defer() + return + container = event.workload + entrypoint = "/opt/oai-gnb/bin/entrypoint.sh" + command = " ".join( + ["/opt/oai-gnb/bin/nr-softmodem.Rel15", "-O", "/opt/oai-gnb/etc/gnb.conf"] + ) + # Patch file to have unique GNB ID + gnb_id = self.unit.name[::-1].split("/")[0][::-1] + gnb_sa_tdd_conf = ( + container.pull("/opt/oai-gnb/etc/gnb.sa.tdd.conf") + .read() + .replace("0xe00", f"0xe0{gnb_id}") + ) + container.push("/opt/oai-gnb/etc/gnb.sa.tdd.conf", gnb_sa_tdd_conf) + pebble_layer = { + "summary": "oai_gnb layer", + "description": "pebble config layer for oai_gnb", + "services": { + "oai_gnb": { + "override": "replace", + "summary": "oai_gnb", + "command": f"{entrypoint} {command}", + "environment": { + "TZ": "Europe/Paris", + "RFSIMULATOR": "server", + "USE_SA_TDD_MONO": "yes", + "GNB_NAME": f'gnb-rfsim-{self.unit.name.replace("/", "-")}', + "MCC": "208", + "MNC": "95", + "MNC_LENGTH": "2", + "TAC": "1", + "NSSAI_SST": "1", + "NSSAI_SD0": "1", + "NSSAI_SD1": "112233", + "GNB_NGA_IF_NAME": "eth0", + "GNB_NGA_IP_ADDRESS": str(pod_ip), + "GNB_NGU_IF_NAME": "eth0", + "GNB_NGU_IP_ADDRESS": str(pod_ip), + "USE_ADDITIONAL_OPTIONS": "--sa -E --rfsim", + }, + } + }, + } + try: + container.add_layer("oai_gnb", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def is_amf_ready(self): + is_ready = ( + self._stored.amf_host + and self._stored.amf_port + and self._stored.amf_api_version + ) + logger.info(f'amf is{" " if is_ready else " not "}ready') + return is_ready + + @property + def is_spgwu_ready(self): + is_ready = self._stored.spgwu_ready + logger.info(f'spgwu is{" " if is_ready else " not "}ready') + return is_ready + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + ip = check_output(["unit-get", "private-address"]).decode().strip() + return IPv4Address(ip) if ip else None + + @property + def container_name(self): + return "gnb" + + @property + def service_name(self): + return "oai_gnb" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._load_amf_data() + self._load_spgwu_data() + + if self.is_amf_ready and self.is_spgwu_ready: + try: + self._configure_service() + if self._start_service(container_name="gnb", service_name="oai_gnb"): + self.unit.status = WaitingStatus( + "waiting 30 seconds for the service to start" + ) + time.sleep(30) + self._provide_service_info(event) + self.unit.status = ActiveStatus() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + else: + self._stop_service(container_name="gnb", service_name="oai_gnb") + self.unit.status = BlockedStatus("need amf and spgwu relations") + + def _load_amf_data(self): + relation = self.framework.model.get_relation("amf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.amf_host = relation_data.get("ip-address") + self._stored.amf_port = relation_data.get("port") + self._stored.amf_api_version = relation_data.get("api-version") + else: + self._stored.amf_host = None + self._stored.amf_port = None + self._stored.amf_api_version = None + + def _load_spgwu_data(self): + relation = self.framework.model.get_relation("spgwu") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.spgwu_ready = relation_data.get("ready") == "True" + else: + self._stored.spgwu_ready = False + + def _configure_service(self): + container = self.unit.get_container("gnb") + if self.service_name in container.get_plan().services: + container.add_layer( + "oai_gnb", + { + "services": { + "oai_gnb": { + "override": "merge", + "environment": { + "AMF_IP_ADDRESS": self._stored.amf_host, + }, + } + }, + }, + combine=True, + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = ( + container.get_service(service_name).is_running() + if service_exists + else False + ) + logger.info(f"service {service_name} exists: {service_exists}") + logger.info(f"container {container_name} is running: {is_running}") + if service_exists and not is_running: + logger.info(f"{container.get_plan()}") + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + ########################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiGnbCharm) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-gnb-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a394aad8a8b18c826543821344bfb1230b64c388 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/README.md @@ -0,0 +1,24 @@ +# oai-nr-ue + +## Description + +TODO: Describe your charm in a few paragraphs of Markdown + +## Usage + +TODO: Provide high-level usage, such as required config or relations + + +## Relations + +TODO: Provide any relations which are provided or required by your charm + +## OCI Images + +TODO: Include a link to the default image your charm uses + +## Contributing + +Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines +on enhancements to this charm following best practice guidelines, and +`CONTRIBUTING.md` for developer guidance. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d5138d0fb05debbf532649f825272e4ed9ed142 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/actions.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7667f713345ffa093da1e18fd7c4379624e0cd23 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/metadata.yaml @@ -0,0 +1,29 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-nr-ue +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + nr-ue: + resource: oai-nr-ue-image + tcpdump: + resource: tcpdump-image +resources: + oai-nr-ue-image: + type: oci-image + description: OCI image for oai-nr-ue (rdefosseoai/oai-nr-ue:develop) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +requires: + gnb: + interface: gnb + limit: 1 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..bfd3c9831214395c791c7a9b67a91454b73eae87 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/charm.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +from typing import Optional +import time + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ConnectionError + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiNrUeCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.nr_ue_pebble_ready: self._on_oai_nr_ue_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.gnb_relation_changed: self._update_service, + self.on.gnb_relation_broken: self._update_service, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + gnb_host=None, + gnb_port=None, + gnb_api_version=None, + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("s1c", 36412, 36412, "UDP"), + ("s1u", 2152, 2152, "UDP"), + ("x2c", 36422, 36422, "UDP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_nr_ue_pebble_ready(self, event): + container = event.workload + entrypoint = "/opt/oai-nr-ue/bin/entrypoint.sh" + command = " ".join( + [ + "/opt/oai-nr-ue/bin/nr-uesoftmodem.Rel15", + "-O", + "/opt/oai-nr-ue/etc/nr-ue-sim.conf", + ] + ) + pebble_layer = { + "summary": "oai_nr_ue layer", + "description": "pebble config layer for oai_nr_ue", + "services": { + "oai_nr_ue": { + "override": "replace", + "summary": "oai_nr_ue", + "command": f"{entrypoint} {command}", + "environment": { + "TZ": "Europe/Paris", + "FULL_IMSI": "208950000000031", + "FULL_KEY": "0C0A34601D4F07677303652C0462535B", + "OPC": "63bfa50ee6523365ff14c1f45f88737d", + "DNN": "oai", + "NSSAI_SST": "1", + "NSSAI_SD": "1", + "USE_ADDITIONAL_OPTIONS": "-E --sa --rfsim -r 106 --numerology 1 -C 3619200000 --nokrnmod", + }, + } + }, + } + try: + container.add_layer("oai_nr_ue", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def is_gnb_ready(self): + return self._stored.gnb_host + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + return IPv4Address( + check_output(["unit-get", "private-address"]).decode().strip() + ) + @property + def container_name(self): + return "nr-ue" + + @property + def service_name(self): + return "oai_nr_ue" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._load_gnb_data() + if self.is_gnb_ready: + try: + self._configure_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + self._start_service(container_name="nr-ue", service_name="oai_nr_ue") + self.unit.status = ActiveStatus() + else: + self._stop_service(container_name="nr-ue", service_name="oai_nr_ue") + self.unit.status = BlockedStatus("need gnb relation") + + def _load_gnb_data(self): + relation = self.framework.model.get_relation("gnb") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.gnb_host = relation_data.get("host") + self._stored.gnb_port = relation_data.get("port") + self._stored.gnb_api_version = relation_data.get("api-version") + else: + self._stored.gnb_host = None + self._stored.gnb_port = None + self._stored.gnb_api_version = None + + def _configure_service(self): + container = self.unit.get_container("nr-ue") + if self.service_name in container.get_plan().services: + container.add_layer( + "oai_nr_ue", + { + "services": { + "oai_nr_ue": { + "override": "merge", + "environment": { + "RFSIMULATOR": self._stored.gnb_host, + }, + } + }, + }, + combine=True, + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = container.get_service(service_name).is_running() + + if service_exists and not is_running: + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + ########################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiNrUeCharm) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nr-ue-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b4bbf8634adb4f26eeccb8a6591cd474e6f1f6a6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/README.md @@ -0,0 +1 @@ +# oai-nrf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..12c6f817be0c665c30cec278c14b613473e54a6b --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/actions.yaml @@ -0,0 +1,11 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + +changeapiversion: + description: "changing api version" \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..79ebc2abe206b3ede7b3292a5bbba335ae6cf56c --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/metadata.yaml @@ -0,0 +1,28 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-nrf +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + nrf: + resource: oai-nrf-image + tcpdump: + resource: tcpdump-image +resources: + oai-nrf-image: + type: oci-image + description: OCI image for oai-nrf (rdefosseoai/oai-nrf:v1.1.0) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +provides: + nrf: + interface: nrf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..86381a2148f4f553d1a4309a17ce81e7159ab2d1 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/charm.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +from typing import Optional +import time + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus +from ops.pebble import ConnectionError + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiNrfCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.nrf_pebble_ready: self._on_oai_nrf_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.nrf_relation_joined: self._provide_service_info, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + self.framework.observe(self.on.changeapiversion_action, self._on_changeapiversion_action) + #################################### + # Observers - Relation Events + #################################### + + #Custom action + def _on_changeapiversion_action(self, event): + try: + container = self.unit.get_container(self.container_name) + config = container.pull('/openair-nrf/etc/nrf.conf').read() + if 'v1' in config: + newconfig = config.replace('v1','v2') + container.push('/openair-nrf/etc/nrf.conf', newconfig) + event.set_results({ "output": f"nrf api version updated successfully" }) + else: + event.set_results({"output": f"nrf api version not updated"}) + except Exception as e: + event.fail(f"update action failed with the following exception: {e}") + + + def _provide_service_info(self, event): + if self.unit.is_leader() and self.is_service_running: + for relation in self.framework.model.relations["nrf"]: + logger.info(f"Found relation {relation.name} with id {relation.id}") + relation.data[self.app]["host"] = self.app.name + relation.data[self.app]["port"] = str(HTTP1_PORT) + relation.data[self.app]["api-version"] = "v1" + else: + logger.info("not relations found") + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("http1", 80, 80, "TCP"), + ("http2", 9090, 9090, "TCP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_nrf_pebble_ready(self, event): + container = event.workload + entrypoint = "/bin/bash /openair-nrf/bin/entrypoint.sh" + command = " ".join( + ["/openair-nrf/bin/oai_nrf", "-c", "/openair-nrf/etc/nrf.conf", "-o"] + ) + pebble_layer = { + "summary": "oai_nrf layer", + "description": "pebble config layer for oai_nrf", + "services": { + "oai_nrf": { + "override": "replace", + "summary": "oai_nrf", + "command": f"{entrypoint} {command}", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + "INSTANCE": "0", + "PID_DIRECTORY": "/var/run", + "NRF_INTERFACE_NAME_FOR_SBI": "eth0", + "NRF_INTERFACE_PORT_FOR_SBI": "80", + "NRF_INTERFACE_HTTP2_PORT_FOR_SBI": "9090", + "NRF_API_VERSION": "v1", + }, + } + }, + } + try: + container.add_layer("oai_nrf", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + return IPv4Address( + check_output(["unit-get", "private-address"]).decode().strip() + ) + + @property + def container_name(self): + return "nrf" + + @property + def service_name(self): + return "oai_nrf" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + if self._start_service(container_name="nrf", service_name="oai_nrf"): + self.unit.status = WaitingStatus( + "waiting 30 seconds for the service to start" + ) + time.sleep(30) + self._provide_service_info(event) + self.unit.status = ActiveStatus() + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = ( + container.get_service(service_name).is_running() + if service_exists + else False + ) + + if service_exists and not is_running: + logger.info(f"{container.get_plan()}") + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + #################################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiNrfCharm, use_juju_for_storage=True) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-nrf-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e22310957b2c342d4f133f07eeef1c42a5a23aa8 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/README.md @@ -0,0 +1 @@ +# oai-smf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d5138d0fb05debbf532649f825272e4ed9ed142 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/actions.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..10f6ffe4fadbab06f59f9ec5c4d4af7362356162 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/metadata.yaml @@ -0,0 +1,35 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-smf +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + smf: + resource: oai-smf-image + tcpdump: + resource: tcpdump-image +resources: + oai-smf-image: + type: oci-image + description: OCI image for oai-smf (rdefosseoai/oai-smf:v1.1.0) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +requires: + nrf: + interface: nrf + limit: 1 + amf: + interface: amf + limit: 1 +provides: + smf: + interface: smf diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b2416508cc7f27fbaff81b6b82f1e47c5f0e3d0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..fded337e25a4dda36f92645e0fe71efd3c93c57f --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/charm.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +from typing import Optional +import time + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ConnectionError + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiSmfCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.smf_pebble_ready: self._on_oai_smf_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.amf_relation_changed: self._update_service, + self.on.amf_relation_broken: self._update_service, + self.on.nrf_relation_changed: self._update_service, + self.on.nrf_relation_broken: self._update_service, + self.on.smf_relation_joined: self._provide_service_info, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + amf_host=None, + amf_port=None, + amf_api_version=None, + nrf_host=None, + nrf_port=None, + nrf_api_version=None, + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + + #################################### + # Observers - Relation Events + #################################### + + def _provide_service_info(self, event): + if self.unit.is_leader() and self.is_service_running: + for relation in self.framework.model.relations["smf"]: + logger.info(f"Found relation {relation.name} with id {relation.id}") + relation.data[self.app]["ready"] = str(True) + else: + logger.info("not relations found") + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("oai-smf", 8805, 8805, "UDP"), + ("http1", 80, 80, "TCP"), + ("http2", 9090, 9090, "TCP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_smf_pebble_ready(self, event): + container = event.workload + entrypoint = "/bin/bash /openair-smf/bin/entrypoint.sh" + command = " ".join( + ["/openair-smf/bin/oai_smf", "-c", "/openair-smf/etc/smf.conf", "-o"] + ) + pebble_layer = { + "summary": "oai_smf layer", + "description": "pebble config layer for oai_smf", + "services": { + "oai_smf": { + "override": "replace", + "summary": "oai_smf", + "command": f"{entrypoint} {command}", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + "INSTANCE": "0", + "PID_DIRECTORY": "/var/run", + "SMF_INTERFACE_NAME_FOR_N4": "eth0", + "SMF_INTERFACE_NAME_FOR_SBI": "eth0", + "SMF_INTERFACE_PORT_FOR_SBI": "80", + "SMF_INTERFACE_HTTP2_PORT_FOR_SBI": "9090", + "SMF_API_VERSION": "v1", + "DEFAULT_DNS_IPV4_ADDRESS": "8.8.8.8", + "DEFAULT_DNS_SEC_IPV4_ADDRESS": "8.8.4.4", + "REGISTER_NRF": "yes", + "DISCOVER_UPF": "yes", + "USE_FQDN_DNS": "yes", + "UDM_IPV4_ADDRESS": "127.0.0.1", + "UDM_PORT": "80", + "UDM_API_VERSION": "v1", + "UDM_FQDN": "localhost", + "UPF_IPV4_ADDRESS": "127.0.0.1", + "UPF_FQDN_0": "localhost", + }, + } + }, + } + try: + container.add_layer("oai_smf", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def is_amf_ready(self): + return ( + self._stored.amf_host + and self._stored.amf_port + and self._stored.amf_api_version + ) + + @property + def is_nrf_ready(self): + return ( + self._stored.nrf_host + and self._stored.nrf_port + and self._stored.nrf_api_version + ) + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + ip = check_output(["unit-get", "private-address"]).decode().strip() + return IPv4Address(ip) if ip else None + + @property + def container_name(self): + return "smf" + + @property + def service_name(self): + return "oai_smf" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._load_amf_data() + self._load_nrf_data() + if self.is_nrf_ready and self.is_amf_ready: + try: + self._configure_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self._start_service(container_name="smf", service_name="oai_smf"): + self._provide_service_info(event) + self.unit.status = ActiveStatus() + else: + self._stop_service(container_name="smf", service_name="oai_smf") + self.unit.status = BlockedStatus("need nrf and amf relations") + + def _load_amf_data(self): + relation = self.framework.model.get_relation("amf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.amf_host = relation_data.get("host") + self._stored.amf_port = relation_data.get("port") + self._stored.amf_api_version = relation_data.get("api-version") + else: + self._stored.amf_host = None + self._stored.amf_port = None + self._stored.amf_api_version = None + + def _load_nrf_data(self): + relation = self.framework.model.get_relation("nrf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.nrf_host = relation_data.get("host") + self._stored.nrf_port = relation_data.get("port") + self._stored.nrf_api_version = relation_data.get("api-version") + else: + self._stored.nrf_host = None + self._stored.nrf_port = None + self._stored.nrf_api_version = None + + def _configure_service(self): + container = self.unit.get_container("smf") + if self.service_name in container.get_plan().services: + container.add_layer( + "oai_smf", + { + "services": { + "oai_smf": { + "override": "merge", + "environment": { + "NRF_FQDN": self._stored.nrf_host, + "NRF_IPV4_ADDRESS": "127.0.0.1", + "NRF_PORT": self._stored.nrf_port, + "NRF_API_VERSION": self._stored.nrf_api_version, + "AMF_IPV4_ADDRESS": "127.0.0.1", + "AMF_PORT": self._stored.amf_port, + "AMF_API_VERSION": self._stored.amf_api_version, + "AMF_FQDN": self._stored.amf_host, + }, + } + }, + }, + combine=True, + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = container.get_service(service_name).is_running() + + if service_exists and not is_running: + logger.info(f"{container.get_plan()}") + container.start(service_name) + time.sleep(30) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + ########################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiSmfCharm) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-smf-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/CONTRIBUTING.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3bba37ce78b553a8a8acfd12dc3eafd416ad78c7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# oai-amf + +## Developing + +Create and activate a virtualenv with the development requirements: + + virtualenv -p python3 venv + source venv/bin/activate + pip install -r requirements-dev.txt + +## Code overview + +TEMPLATE-TODO: +One of the most important things a consumer of your charm (or library) +needs to know is what set of functionality it provides. Which categories +does it fit into? Which events do you listen to? Which libraries do you +consume? Which ones do you export and how are they used? + +## Intended use case + +TEMPLATE-TODO: +Why were these decisions made? What's the scope of your charm? + +## Roadmap + +If this Charm doesn't fulfill all of the initial functionality you were +hoping for or planning on, please add a Roadmap or TODO here + +## Testing + +The Python operator framework includes a very nice harness for testing +operator behaviour without full deployment. Just `run_tests`: + + ./run_tests diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/LICENSE b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/LICENSE @@ -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. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/README.md b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6713c5e17a7c55ec98319b448ead5102e05912d5 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/README.md @@ -0,0 +1,24 @@ +# oai-amf + +## Description + +TODO: Describe your charm in a few paragraphs of Markdown + +## Usage + +TODO: Provide high-level usage, such as required config or relations + + +## Relations + +TODO: Provide any relations which are provided or required by your charm + +## OCI Images + +TODO: Include a link to the default image your charm uses + +## Contributing + +Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines +on enhancements to this charm following best practice guidelines, and +`CONTRIBUTING.md` for developer guidance. diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/actions.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/actions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d5138d0fb05debbf532649f825272e4ed9ed142 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/actions.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need actions, you can remove the file entirely. +# It ties in to the example _on_fortune_action handler in src/charm.py +# +# Learn more about actions at: https://juju.is/docs/sdk/actions + diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/charmcraft.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/charmcraft.yaml new file mode 100644 index 0000000000000000000000000000000000000000..048d45441c7e0645e78ac0b7b392bb510408b49a --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/charmcraft.yaml @@ -0,0 +1,10 @@ +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + run-on: + - name: "ubuntu" + channel: "20.04" diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/config.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..49750db07c7f54db6588a9a3d226f20eda367b58 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# TEMPLATE-TODO: change this example to suit your needs. +# If you don't need a config, you can remove the file entirely. +# It ties in to the example _on_config_changed handler in src/charm.py +# +# Learn more about config at: https://juju.is/docs/sdk/config + +options: + start-tcpdump: + default: False + description: | + start tcpdump collection to analyse but beware + it will take a lot of space in the container/persistent volume. + type: boolean diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/metadata.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/metadata.yaml new file mode 100644 index 0000000000000000000000000000000000000000..90fff95d9bf6115560973fa2bb725239aaa6cdbf --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/metadata.yaml @@ -0,0 +1,35 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +# For a complete list of supported options, see: +# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15 +name: oai-spgwu-tiny +display-name: | + TEMPLATE-TODO: fill out a display name for the Charmcraft store +description: | + TEMPLATE-TODO: fill out the charm's description +summary: | + TEMPLATE-TODO: fill out the charm's summary + +containers: + spgwu-tiny: + resource: oai-spgwu-tiny-image + tcpdump: + resource: tcpdump-image +resources: + oai-spgwu-tiny-image: + type: oci-image + description: OCI image for oai-spgwu-tiny (rdefosseoai/oai-spgwu-tiny:v1.1.2) + tcpdump-image: + type: oci-image + description: OCI image for tcpdump (corfr/tcpdump:latest) +requires: + nrf: + interface: nrf + limit: 1 + smf: + interface: smf + limit: 1 +provides: + spgwu: + interface: spgwu diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements-dev.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f2a3f5bcd8cf79122b586c767c812b481079593 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +coverage +flake8 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements.txt b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..964c4f2671937af376b8ed75ed4bb8a4202d4ebd --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/requirements.txt @@ -0,0 +1,2 @@ +ops >= 1.2.0 +kubernetes \ No newline at end of file diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/run_tests b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/run_tests new file mode 100755 index 0000000000000000000000000000000000000000..d59be2c6515e7a46a07f73e86490f2d0eb445094 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/run_tests @@ -0,0 +1,17 @@ +#!/bin/sh -e +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. + +if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then + . venv/bin/activate +fi + +if [ -z "$PYTHONPATH" ]; then + export PYTHONPATH="lib:src" +else + export PYTHONPATH="lib:src:$PYTHONPATH" +fi + +flake8 +coverage run --branch --source=src -m unittest -v "$@" +coverage report -m diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/charm.py new file mode 100755 index 0000000000000000000000000000000000000000..71a10f9859b9d2a08dbd3f531c42fd0552dd5925 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/charm.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + + https://discourse.charmhub.io/t/4208 +""" + + +from ipaddress import IPv4Address +import logging +from subprocess import check_output +import time +from typing import Optional + +from kubernetes import kubernetes +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ConnectionError + + +from kubernetes_service import K8sServicePatch, PatchFailed + +logger = logging.getLogger(__name__) + +SCTP_PORT = 38412 +HTTP1_PORT = 80 +HTTP2_PORT = 9090 + + +class OaiSpgwuTinyCharm(CharmBase): + """Charm the service.""" + + _stored = StoredState() + + def __init__(self, *args): + super().__init__(*args) + event_observer_mapping = { + self.on.spgwu_tiny_pebble_ready: self._on_oai_spgwu_tiny_pebble_ready, + self.on.tcpdump_pebble_ready: self._on_tcpdump_pebble_ready, + self.on.install: self._on_install, + self.on.config_changed: self._on_config_changed, + self.on.spgwu_relation_joined: self._provide_service_info, + self.on.nrf_relation_changed: self._update_service, + self.on.nrf_relation_broken: self._update_service, + self.on.smf_relation_changed: self._update_service, + self.on.smf_relation_broken: self._update_service, + } + for event, observer in event_observer_mapping.items(): + self.framework.observe(event, observer) + self._stored.set_default( + nrf_host=None, + nrf_port=None, + nrf_api_version=None, + smf_ready=False, + _k8s_stateful_patched=False, + _k8s_authed=False, + ) + + #################################### + # Observers - Relation Events + #################################### + + def _provide_service_info(self, event): + if self.unit.is_leader() and self.is_service_running: + for relation in self.framework.model.relations["spgwu"]: + logger.info(f"Found relation {relation.name} with id {relation.id}") + relation.data[self.app]["ready"] = str(True) + else: + logger.info("not relations found") + + #################################### + # Observers - Charm Events + #################################### + + def _on_install(self, event): + self._k8s_auth() + self._patch_stateful_set() + K8sServicePatch.set_ports( + self.app.name, + [ + ("oai-spgwu-tiny", 8805, 8805, "UDP"), + ("s1u", 2152, 2152, "UDP"), + ("iperf", 5001, 5001, "UDP"), + ], + ) + + def _on_config_changed(self, event): + self._update_tcpdump_service(event) + + #################################### + # Observers - Pebble Events + #################################### + + def _on_oai_spgwu_tiny_pebble_ready(self, event): + container = event.workload + entrypoint = "/bin/bash /openair-spgwu-tiny/bin/entrypoint.sh" + command = " ".join( + [ + "/openair-spgwu-tiny/bin/oai_spgwu", + "-c", + "/openair-spgwu-tiny/etc/spgw_u.conf", + "-o", + ] + ) + pebble_layer = { + "summary": "oai_spgwu_tiny layer", + "description": "pebble config layer for oai_spgwu_tiny", + "services": { + "oai_spgwu_tiny": { + "override": "replace", + "summary": "oai_spgwu_tiny", + "command": f"{entrypoint} {command}", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + "GW_ID": "1", + "MCC": "208", + "MNC03": "95", + "REALM": "3gpp.org", + "PID_DIRECTORY": "/var/run", + "SGW_INTERFACE_NAME_FOR_S1U_S12_S4_UP": "eth0", + "THREAD_S1U_PRIO": "98", + "S1U_THREADS": "1", + "SGW_INTERFACE_NAME_FOR_SX": "eth0", + "THREAD_SX_PRIO": "98", + "SX_THREADS": "1", + "PGW_INTERFACE_NAME_FOR_SGI": "eth0", + "THREAD_SGI_PRIO": "98", + "SGI_THREADS": "1", + "NETWORK_UE_NAT_OPTION": "yes", + "GTP_EXTENSION_HEADER_PRESENT": "yes", + "NETWORK_UE_IP": "12.1.1.0/24", + "SPGWC0_IP_ADDRESS": "127.0.0.1", + "BYPASS_UL_PFCP_RULES": "no", + "ENABLE_5G_FEATURES": "yes", + "NSSAI_SST_0": "1", + "NSSAI_SD_0": "1", + "DNN_0": "oai", + "UPF_FQDN_5G": self.app.name, + }, + } + }, + } + try: + container.add_layer("oai_spgwu_tiny", pebble_layer, combine=True) + self._update_service(event) + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + + def _on_tcpdump_pebble_ready(self, event): + self._update_tcpdump_service(event) + + #################################### + # Properties + #################################### + + @property + def is_nrf_ready(self): + return ( + self._stored.nrf_host + and self._stored.nrf_port + and self._stored.nrf_api_version + ) + + @property + def is_smf_ready(self): + return self._stored.smf_ready + + @property + def namespace(self) -> str: + with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + return f.read().strip() + + @property + def pod_ip(self) -> Optional[IPv4Address]: + return IPv4Address( + check_output(["unit-get", "private-address"]).decode().strip() + ) + + @property + def container_name(self): + return "spgwu-tiny" + + @property + def service_name(self): + return "oai_spgwu_tiny" + + @property + def is_service_running(self): + container = self.unit.get_container(self.container_name) + return ( + self.service_name in container.get_plan().services + and container.get_service(self.service_name).is_running() + ) + + #################################### + # Utils - Services and configuration + #################################### + + def _update_service(self, event): + self._load_nrf_data() + self._load_smf_data() + if self.is_nrf_ready and self.is_smf_ready: + try: + self._configure_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self._start_service( + container_name="spgwu-tiny", service_name="oai_spgwu_tiny" + ): + self._provide_service_info(event) + self.unit.status = ActiveStatus() + else: + self._stop_service( + container_name="spgwu-tiny", service_name="oai_spgwu_tiny" + ) + self.unit.status = BlockedStatus("need nrf and smf relations") + + def _load_nrf_data(self): + relation = self.framework.model.get_relation("nrf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.nrf_host = relation_data.get("host") + self._stored.nrf_port = relation_data.get("port") + self._stored.nrf_api_version = relation_data.get("api-version") + else: + self._stored.nrf_host = None + self._stored.nrf_port = None + self._stored.nrf_api_version = None + + def _load_smf_data(self): + relation = self.framework.model.get_relation("smf") + if relation and relation.app in relation.data: + relation_data = relation.data[relation.app] + self._stored.smf_ready = relation_data.get("ready") == "True" + else: + self._stored.smf_ready = False + + def _configure_service(self): + container = self.unit.get_container("spgwu-tiny") + if self.service_name in container.get_plan().services: + container.add_layer( + "oai_spgwu_tiny", + { + "services": { + "oai_spgwu_tiny": { + "override": "merge", + "environment": { + "REGISTER_NRF": "yes", + "USE_FQDN_NRF": "yes", + "NRF_FQDN": self._stored.nrf_host, + "NRF_IPV4_ADDRESS": "127.0.0.1", + "NRF_PORT": self._stored.nrf_port, + "NRF_API_VERSION": self._stored.nrf_api_version, + }, + } + }, + }, + combine=True, + ) + + def _start_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + service_exists = service_name in container.get_plan().services + is_running = container.get_service(service_name).is_running() + + if service_exists and not is_running: + container.start(service_name) + return True + + def _stop_service(self, container_name, service_name): + container = self.unit.get_container(container_name) + is_running = ( + service_name in container.get_plan().services + and container.get_service(service_name).is_running() + ) + if is_running: + container.stop(service_name) + + #################################### + # Utils - TCP Dump configuration + #################################### + + def _update_tcpdump_service(self, event): + try: + self._configure_tcpdump_service() + except ConnectionError: + logger.info("pebble socket not available, deferring config-changed") + event.defer() + return + if self.config["start-tcpdump"]: + self._start_service("tcpdump", "tcpdump") + else: + self._stop_service("tcpdump", "tcpdump") + + def _configure_tcpdump_service(self): + container = self.unit.get_container("tcpdump") + container.add_layer( + "tcpdump", + { + "summary": "tcpdump layer", + "description": "pebble config layer for tcpdump", + "services": { + "tcpdump": { + "override": "replace", + "summary": "tcpdump", + "command": f"/usr/sbin/tcpdump -i any -w /pcap_{self.app.name}.pcap", + "environment": { + "DEBIAN_FRONTEND": "noninteractive", + "TZ": "Europe/Paris", + }, + } + }, + }, + combine=True, + ) + + #################################### + # Utils - K8s authentication + #################################### + + def _k8s_auth(self) -> bool: + """Authenticate to kubernetes.""" + if self._stored._k8s_authed: + return True + kubernetes.config.load_incluster_config() + self._stored._k8s_authed = True + + def _patch_stateful_set(self) -> None: + """Patch the StatefulSet to include specific ServiceAccount and Secret mounts""" + if self._stored._k8s_stateful_patched: + return + + # Get an API client + api = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient()) + for attempt in range(5): + try: + self.unit.status = MaintenanceStatus( + f"patching StatefulSet for additional k8s permissions. Attempt {attempt+1}/5" + ) + s = api.read_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace + ) + # Add the required security context to the container spec + s.spec.template.spec.containers[1].security_context.privileged = True + + # Patch the StatefulSet with our modified object + api.patch_namespaced_stateful_set( + name=self.app.name, namespace=self.namespace, body=s + ) + logger.info( + "Patched StatefulSet to include additional volumes and mounts" + ) + self._stored._k8s_stateful_patched = True + return + except Exception as e: + self.unit.status = MaintenanceStatus( + "failed patching StatefulSet... Retrying in 10 seconds" + ) + time.sleep(5) + + +if __name__ == "__main__": + main(OaiSpgwuTinyCharm, use_juju_for_storage=True) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/kubernetes_service.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/kubernetes_service.py new file mode 100644 index 0000000000000000000000000000000000000000..254071bb4f663f94c2d2093dc6c964bf8797f4b6 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/src/kubernetes_service.py @@ -0,0 +1,96 @@ +# Copyright 2021 Canonical Ltd. +# See LICENSE file for licensing details. + +"""A simple class used for patching incorrect Kubernetes Service definitions created by Juju.""" + +from typing import List, Tuple + +import kubernetes + + +class PatchFailed(RuntimeError): + """Patching the kubernetes service failed.""" + + +class K8sServicePatch: + """A utility for patching the Kubernetes service set up by Juju. + Attributes: + namespace_file (str): path to the k8s namespace file in the charm container + """ + + namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + @staticmethod + def namespace() -> str: + """Read the Kubernetes namespace we're deployed in from the mounted service token. + Returns: + str: The current Kubernetes namespace + """ + with open(K8sServicePatch.namespace_file, "r") as f: + return f.read().strip() + + @staticmethod + def _k8s_service( + app: str, service_ports: List[Tuple[str, int, int, str]] + ) -> kubernetes.client.V1Service: + """Property accessor to return a valid Kubernetes Service representation for Alertmanager. + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Returns: + kubernetes.client.V1Service: A Kubernetes Service with correctly annotated metadata and + ports. + """ + ports = [ + kubernetes.client.V1ServicePort( + name=port[0], port=port[1], target_port=port[2], protocol=port[3] + ) + for port in service_ports + ] + + ns = K8sServicePatch.namespace() + return kubernetes.client.V1Service( + api_version="v1", + metadata=kubernetes.client.V1ObjectMeta( + namespace=ns, + name=app, + labels={"app.kubernetes.io/name": app}, + ), + spec=kubernetes.client.V1ServiceSpec( + ports=ports, + selector={"app.kubernetes.io/name": app}, + ), + ) + + @staticmethod + def set_ports(app: str, service_ports: List[Tuple[str, int, int, str]]): + """Patch the Kubernetes service created by Juju to map the correct port. + Currently, Juju uses port 65535 for all endpoints. This can be observed via: + kubectl describe services -n | grep Port -C 2 + At runtime, pebble watches which ports are bound and we need to patch the gap for pebble + not telling Juju to fix the K8S Service definition. + Typical usage example from within charm code (e.g. on_install): + service_ports = [("my-app-api", 9093, 9093), ("my-app-ha", 9094, 9094)] + K8sServicePatch.set_ports(self.app.name, service_ports) + Args: + app: app name + service_ports: a list of tuples (name, port, target_port) for every service port. + Raises: + PatchFailed: if patching fails. + """ + # First ensure we're authenticated with the Kubernetes API + + ns = K8sServicePatch.namespace() + # Set up a Kubernetes client + api = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient()) + try: + # Delete the existing service so we can redefine with correct ports + # I don't think you can issue a patch that *replaces* the existing ports, + # only append + api.delete_namespaced_service(name=app, namespace=ns) + # Recreate the service with the correct ports for the application + api.create_namespaced_service( + namespace=ns, body=K8sServicePatch._k8s_service(app, service_ports) + ) + except kubernetes.client.exceptions.ApiException as e: + raise PatchFailed("Failed to patch k8s service: {}".format(e)) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/tests/__init__.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/tests/test_charm.py b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/tests/test_charm.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1be8cf01f4a4a23eaf6a0699856139e04b1ef0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/charms/oai-bundle/oai-spgwu-tiny-operator/tests/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2021 David Garcia +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest +from unittest.mock import Mock + +from charm import OaiAmfCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(OaiAmfCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_config_changed(self): + self.assertEqual(list(self.harness.charm._stored.things), []) + self.harness.update_config({"thing": "foo"}) + self.assertEqual(list(self.harness.charm._stored.things), ["foo"]) + + def test_action(self): + # the harness doesn't (yet!) help much with actions themselves + action_event = Mock(params={"fail": ""}) + self.harness.charm._on_fortune_action(action_event) + + self.assertTrue(action_event.set_results.called) + + def test_action_fail(self): + action_event = Mock(params={"fail": "fail this"}) + self.harness.charm._on_fortune_action(action_event) + + self.assertEqual(action_event.fail.call_args, [("fail this",)]) + + def test_httpbin_pebble_ready(self): + # Check the initial Pebble plan is empty + initial_plan = self.harness.get_container_pebble_plan("httpbin") + self.assertEqual(initial_plan.to_yaml(), "{}\n") + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"thing": "🎁"}, + } + }, + } + # Get the httpbin container from the model + container = self.harness.model.unit.get_container("httpbin") + # Emit the PebbleReadyEvent carrying the httpbin container + self.harness.charm.on.httpbin_pebble_ready.emit(container) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service( + "httpbin" + ) + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/bundle.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/bundle.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d2af35c776b9ce2ecec08843e2fc6981487f4e4c --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/bundle.yaml @@ -0,0 +1,76 @@ +bundle: kubernetes +applications: + nrf: + charm: ./oai-nrf-operator/nrf.charm + scale: 1 + trust: true + options: + start-tcpdump: true + resources: + oai-nrf-image: rdefosseoai/oai-nrf:v1.1.0 + tcpdump-image: corfr/tcpdump:latest + amf: + charm: ./oai-amf-operator/amf.charm + trust: true + options: + start-tcpdump: true + resources: + oai-amf-image: rdefosseoai/oai-amf:v1.1.0 + tcpdump-image: corfr/tcpdump:latest + scale: 1 + smf: + charm: ./oai-smf-operator/smf.charm + scale: 1 + trust: true + options: + start-tcpdump: true + resources: + oai-smf-image: rdefosseoai/oai-smf:develop + tcpdump-image: corfr/tcpdump:latest + spgwu-tiny: + charm: ./oai-spgwu-tiny-operator/spgwu-tiny.charm + scale: 1 + trust: true + options: + start-tcpdump: true + resources: + oai-spgwu-tiny-image: rdefosseoai/oai-spgwu-tiny:v1.1.2 + tcpdump-image: corfr/tcpdump:latest + db: + charm: ./oai-db-operator/db.charm + scale: 1 + resources: + oai-db-image: mysql:5.5 + gnb: + charm: ./oai-gnb-operator/gnb.charm + scale: 1 + trust: true + resources: + oai-gnb-image: rdefosseoai/oai-gnb:develop + tcpdump-image: corfr/tcpdump:latest + nr-ue: + charm: ./oai-nr-ue-operator/nr-ue.charm + scale: 1 + trust: true + resources: + oai-nr-ue-image: rdefosseoai/oai-nr-ue:develop + tcpdump-image: corfr/tcpdump:latest +relations: + - - db:db + - amf:db + - - nrf:nrf + - amf:nrf + - - nrf:nrf + - smf:nrf + - - smf:amf + - amf:amf + - - nrf:nrf + - spgwu-tiny:nrf + - - spgwu-tiny:smf + - smf:smf + - - gnb:amf + - amf:amf + - - nr-ue:gnb + - gnb:gnb + - - spgwu-tiny:spgwu + - gnb:spgwu diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-amf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-db-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-gnb-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nr-ue-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-nrf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-smf-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.flake8 b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8ef84fcd43f3b7a46768c31b20f36cab48ffdfe0 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 99 +select: E,W,F,C,N +exclude: + venv + .git + build + dist + *.egg_info diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.gitignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c3f0e5ed7915b3800370e2f37f7661e29df6742 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm + +.coverage +__pycache__/ +*.py[cod] diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.jujuignore b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.jujuignore new file mode 100644 index 0000000000000000000000000000000000000000..6ccd559eabeae93e4d23215fa450130fa9b37ace --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/juju-bundles/oai-spgwu-tiny-operator/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/oai_cn5g_cnf1.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/oai_cn5g_cnf1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e13c3a0e82dccd163cf3c2c00d7b2ed24b414d0c --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_cnf1/oai_cn5g_cnf1.yaml @@ -0,0 +1,93 @@ +vnfd: + df: + - id: default-df + kdu-resource-profile: + - id: amf-scale + kdu-name: native-kdu + resource-name: amf + min-number-of-instances: 1 + max-number-of-instances: 5 + - id: smf-scale + kdu-name: native-kdu + resource-name: smf + min-number-of-instances: 1 + max-number-of-instances: 5 + - id: nrf-scale + kdu-name: native-kdu + resource-name: nrf + min-number-of-instances: 1 + max-number-of-instances: 5 + - id: gnb-scale + kdu-name: native-kdu + resource-name: gnb + min-number-of-instances: 1 + max-number-of-instances: 5 + scaling-aspect: + - id: scale-amf + name: scale-amf + max-scale-level: 10 + aspect-delta-details: + deltas: + - id: kdu-delta + kdu-resource-delta: + - id: amf-scale + number-of-instances: 1 + - id: scale-smf + name: scale-smf + max-scale-level: 10 + aspect-delta-details: + deltas: + - id: kdu-delta + kdu-resource-delta: + - id: smf-scale + number-of-instances: 1 + - id: scale-nrf + name: scale-nrf + max-scale-level: 10 + aspect-delta-details: + deltas: + - id: kdu-delta + kdu-resource-delta: + - id: nrf-scale + number-of-instances: 1 + - id: scale-gnb + name: scale-gnb + max-scale-level: 10 + aspect-delta-details: + deltas: + - id: kdu-delta + kdu-resource-delta: + - id: gnb-scale + number-of-instances: 1 + lcm-operations-configuration: + operate-vnf-op-config: + day1-2: + - id: native-kdu + config-primitive: + - name: starttcpdump + parameter: + - name: application-name + data-type: STRING + default-value: amf + - name: stoptcpdump + parameter: + - name: application-name + data-type: STRING + default-value: amf + - name: changeapiversion + parameter: + - name: application-name + data-type: STRING + default-value: nrf + ext-cpd: + - id: mgmt-ext + k8s-cluster-net: mgmtnet + id: oai_cn5g_cnf1 + k8s-cluster: + nets: + - id: mgmtnet + kdu: + - name: native-kdu + juju-bundle: bundle.yaml + mgmt-cp: mgmt-ext + product-name: oai_cn5g_cnf1 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_ns1/oai_cn5g_ns1.yaml b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_ns1/oai_cn5g_ns1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9911314d3abf5b202aa555519dbda1338ba12cd5 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/oai_cn5g_ns1/oai_cn5g_ns1.yaml @@ -0,0 +1,30 @@ +nsd: + nsd: + - description: oai cn5g ns with 7 kdu connected two (mgmt&data) VLs + df: + - id: default-df + virtual-link-profile: + - id: vlp-datanet + virtual-link-desc-id: datanet + virtual-link-protocol-data: + associated-layer-protocol: ipv4 + l3-protocol-data: + ip-version: ipv4 + cidr: 192.168.10.0/24 + vnf-profile: + - id: oai_cn5g_core + virtual-link-connectivity: + - constituent-cpd-id: + - constituent-base-element-id: oai_cn5g_cnf1 + constituent-cpd-id: mgmt-ext + virtual-link-profile-id: mgmtnet + vnfd-id: oai_cn5g_cnf1 + id: oai_cn5g_ns1 + name: oai_cn5g_ns1 + version: '1.0' + virtual-link-desc: + - id: mgmtnet + mgmt-network: true + - id: datanet + vnfd-id: + - oai_cn5g_cnf1 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/onboard.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/onboard.sh new file mode 100755 index 0000000000000000000000000000000000000000..b263acf6d812e2310054f01595f5a3472a62ad68 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/onboard.sh @@ -0,0 +1 @@ +osm ns-create --ns_name 5gcore_panther_team5 --nsd_name oai_cn5g_ns1 --vim_account hackfest diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/remove.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/remove.sh new file mode 100755 index 0000000000000000000000000000000000000000..69a714fd6eeea9a3514fd5e45b716114990932d4 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/remove.sh @@ -0,0 +1,2 @@ +osm nsd-delete oai_cn5g_ns1 +osm vnfd-delete oai_cn5g_cnf1 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/tar.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/tar.sh new file mode 100755 index 0000000000000000000000000000000000000000..96686acf4863cb94a2ae1e6cb131e38053dd9832 --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/tar.sh @@ -0,0 +1,2 @@ +tar -czvf oai_cn5g_cnf1.tar.gz oai_cn5g_cnf1 +#tar -czvf oai_cn5g_ns1.tar.gz oai_cn5g_ns1 diff --git a/Hackfest_Demos/OSM-MR11/Team-5-Panther/uplaod.sh b/Hackfest_Demos/OSM-MR11/Team-5-Panther/uplaod.sh new file mode 100755 index 0000000000000000000000000000000000000000..c849d27d26657f1157c395b0538f4be9602c1c8e --- /dev/null +++ b/Hackfest_Demos/OSM-MR11/Team-5-Panther/uplaod.sh @@ -0,0 +1,2 @@ +osm vnfd-create oai_cn5g_cnf1.tar.gz +osm nsd-create oai_cn5g_ns1.tar.gz