From: Admin Date: Fri, 23 Jun 2017 08:34:07 +0000 (+0200) Subject: Imported repo juju-charms X-Git-Tag: v2.0.2~7^2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=7873b95378885e2f226ff7c33abc96a6883c107b;p=osm%2Fdevops.git Imported repo juju-charms --- diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9421a45a..00000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -builds/ -deps/ diff --git a/Makefile b/Makefile deleted file mode 100644 index a579390f..00000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2016 RIFT.io Inc -# -# 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. -# -# - -BUILD_DIR = . - -CHARMS:= vpe-router vyos-proxy pingpong flownac sandvine-pts-proxy -CHARM_SRC_DIR := layers -CHARM_BUILD_DIR := $(BUILD_DIR)/builds - -CHARM_SRC_DIRS := $(addprefix $(CHARM_SRC_DIR)/, $(CHARMS)) -CHARM_BUILD_DIRS := $(addprefix $(CHARM_BUILD_DIR)/, $(CHARMS)) - -all: $(CHARM_BUILD_DIRS) - -clean: - -@ $(RM) -rf $(CHARM_BUILD_DIR) - -$(CHARM_BUILD_DIR)/%: $(CHARM_SRC_DIR)/% - charm-build -o $(BUILD_DIR) $< diff --git a/README.md b/README.md deleted file mode 100644 index 6eaf43f8..00000000 --- a/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# Juju Charm usage and development - -This document is intended to provide a brief overview of the components included -in this repository as well as recommendations for how to develop, build, and -publish charms. - -Please read the [develper geting started guide](https://jujucharms.com/docs/2.0/developer-getting-started) before proceeding. - -## Directory structure - -``` -. -├── builds -│   └── vpe-router -├── interfaces -├── layers -│   └── vpe-router -└── module-blueprints -``` - -The source code of a charm is referred to as a "layer". This layer is compiled -into a charm and placed in the `builds/` directory. Interfaces, currently -unused in this context, extend relationships between applications. - -## Development workflow -### Prepare your build environment -``` -# Source the environment variables JUJU_REPOSITORY, INTERFACE_PATH, and -# LAYER_PATH, which are needed to build a charm. You could also place these -# in your $HOME/.bashrc -$ source juju-env.sh -``` -#### Install the `charm` command, either via apt: - -``` -$ sudo apt install charm -``` - -or with [snap](http://snapcraft.io/) - -``` -$ snap install charm --edge -``` - -To build a charm, simply run `charm build` inside of a layer. -``` -$ cd $LAYER_PATH/vpe-router -$ charm build -$ charm deploy $JUJU_REPOSITORY/builds/vpe-router -``` - -## Publishing to jujucharms.com - -Publishing to the Juju Charm store requires a launchpad login. With that, login -to [jujucharms.com](http://www.jujucharms.com/). - -Next, you'll use the charm command to publish your compiled charm. This will -put the charm into the store where it can be used by anyone with access. - -For example, if I wanted to publish the latest version of the vpe-router charm: - -# Step 1: Upload the charm to the "unpublished" channel -``` -$ cd $JUJU_REPOSITORY/builds -$ charm push vpe-router/ cs:~aisrael/vpe-router -url: cs:~aisrael/vpe-router-0 -channel: unpublished -``` - -# There are four channels to release a charm: edge, beta, candidate, and stable -``` -$ charm release cs:~aisrael/vpe-router-0 --channel=edge -url: cs:~aisrael/vpe-router-0 -channel: edge -``` -The charm can then be deployed directly from the charm store: -``` -$ juju deploy cs:~aisrael/vpe-router --channel=edge -``` diff --git a/juju-charms/Makefile b/juju-charms/Makefile new file mode 100644 index 00000000..a579390f --- /dev/null +++ b/juju-charms/Makefile @@ -0,0 +1,33 @@ +# +# Copyright 2016 RIFT.io Inc +# +# 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. +# +# + +BUILD_DIR = . + +CHARMS:= vpe-router vyos-proxy pingpong flownac sandvine-pts-proxy +CHARM_SRC_DIR := layers +CHARM_BUILD_DIR := $(BUILD_DIR)/builds + +CHARM_SRC_DIRS := $(addprefix $(CHARM_SRC_DIR)/, $(CHARMS)) +CHARM_BUILD_DIRS := $(addprefix $(CHARM_BUILD_DIR)/, $(CHARMS)) + +all: $(CHARM_BUILD_DIRS) + +clean: + -@ $(RM) -rf $(CHARM_BUILD_DIR) + +$(CHARM_BUILD_DIR)/%: $(CHARM_SRC_DIR)/% + charm-build -o $(BUILD_DIR) $< diff --git a/juju-charms/README.md b/juju-charms/README.md new file mode 100644 index 00000000..6eaf43f8 --- /dev/null +++ b/juju-charms/README.md @@ -0,0 +1,79 @@ +# Juju Charm usage and development + +This document is intended to provide a brief overview of the components included +in this repository as well as recommendations for how to develop, build, and +publish charms. + +Please read the [develper geting started guide](https://jujucharms.com/docs/2.0/developer-getting-started) before proceeding. + +## Directory structure + +``` +. +├── builds +│   └── vpe-router +├── interfaces +├── layers +│   └── vpe-router +└── module-blueprints +``` + +The source code of a charm is referred to as a "layer". This layer is compiled +into a charm and placed in the `builds/` directory. Interfaces, currently +unused in this context, extend relationships between applications. + +## Development workflow +### Prepare your build environment +``` +# Source the environment variables JUJU_REPOSITORY, INTERFACE_PATH, and +# LAYER_PATH, which are needed to build a charm. You could also place these +# in your $HOME/.bashrc +$ source juju-env.sh +``` +#### Install the `charm` command, either via apt: + +``` +$ sudo apt install charm +``` + +or with [snap](http://snapcraft.io/) + +``` +$ snap install charm --edge +``` + +To build a charm, simply run `charm build` inside of a layer. +``` +$ cd $LAYER_PATH/vpe-router +$ charm build +$ charm deploy $JUJU_REPOSITORY/builds/vpe-router +``` + +## Publishing to jujucharms.com + +Publishing to the Juju Charm store requires a launchpad login. With that, login +to [jujucharms.com](http://www.jujucharms.com/). + +Next, you'll use the charm command to publish your compiled charm. This will +put the charm into the store where it can be used by anyone with access. + +For example, if I wanted to publish the latest version of the vpe-router charm: + +# Step 1: Upload the charm to the "unpublished" channel +``` +$ cd $JUJU_REPOSITORY/builds +$ charm push vpe-router/ cs:~aisrael/vpe-router +url: cs:~aisrael/vpe-router-0 +channel: unpublished +``` + +# There are four channels to release a charm: edge, beta, candidate, and stable +``` +$ charm release cs:~aisrael/vpe-router-0 --channel=edge +url: cs:~aisrael/vpe-router-0 +channel: edge +``` +The charm can then be deployed directly from the charm store: +``` +$ juju deploy cs:~aisrael/vpe-router --channel=edge +``` diff --git a/juju-charms/juju-env.sh b/juju-charms/juju-env.sh new file mode 100644 index 00000000..59fa9e71 --- /dev/null +++ b/juju-charms/juju-env.sh @@ -0,0 +1,4 @@ +# Set the Juju env variables for building a layer +export JUJU_REPOSITORY=`pwd` +export INTERFACE_PATH=$JUJU_REPOSITORY/interfaces +export LAYER_PATH=$JUJU_REPOSITORY/layers diff --git a/juju-charms/layers/netutils/LICENSE b/juju-charms/layers/netutils/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/juju-charms/layers/netutils/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/juju-charms/layers/netutils/README.md b/juju-charms/layers/netutils/README.md new file mode 100644 index 00000000..e8258c03 --- /dev/null +++ b/juju-charms/layers/netutils/README.md @@ -0,0 +1,64 @@ +# Overview + +This charm provides basic network utilities that can be run from a Juju-deployed +machine. + +# Usage + +To deploy the charm: +```bash +$ juju deploy cs:~nfv/netutils +``` + +To run an action: +```bash +$ juju run-action netutils/0 ping destination=google.com +$ juju run-action netutils/0 traceroute destination=google.com +``` + +To fetch the output of an action: +```bash +$ juju show-action-output 026b3d4c-0bb2-4818-8d24-9855936cdcdf +results: + output: | + traceroute to google.com (216.58.198.78), 30 hops max, 60 byte packets + 1 ec2-79-125-0-86.eu-west-1.compute.amazonaws.com (79.125.0.86) 1.431 ms 1.410 ms 1.380 ms + 2 100.64.2.73 (100.64.2.73) 1.647 ms 100.64.2.103 (100.64.2.103) 1.247 ms 100.64.2.121 (100.64.2.121) 1.224 ms + 3 100.64.0.232 (100.64.0.232) 1.296 ms 100.64.0.184 (100.64.0.184) 1.515 ms 100.64.0.234 (100.64.0.234) 1.079 ms + 4 100.64.16.37 (100.64.16.37) 0.377 ms 100.64.16.49 (100.64.16.49) 0.347 ms 100.64.16.1 (100.64.16.1) 0.340 ms + 5 176.32.107.12 (176.32.107.12) 0.739 ms 176.32.107.4 (176.32.107.4) 0.875 ms 0.748 ms + 6 178.236.0.111 (178.236.0.111) 0.650 ms 0.641 ms 0.645 ms + 7 72.14.215.85 (72.14.215.85) 0.544 ms 1.508 ms 1.498 ms + 8 209.85.252.198 (209.85.252.198) 0.680 ms 0.659 ms 0.618 ms + 9 64.233.174.27 (64.233.174.27) 0.690 ms 0.682 ms 0.634 ms + 10 dub08s02-in-f14.1e100.net (216.58.198.78) 0.568 ms 0.560 ms 0.595 ms +status: completed +timing: + completed: 2016-06-29 14:50:04 +0000 UTC + enqueued: 2016-06-29 14:50:03 +0000 UTC + started: 2016-06-29 14:50:03 +0000 UTC +``` +## iperf3 + +Because iperf3 has a client and server component, the netutils charm can operate +as both. Setting the iperf3 configuration value to True will start iperf3 in +server mode, running as a daemon. +``` +$ juju deploy cs:~nfv/netutils client +$ juju deploy cs:~nfv/netutils server iperf3=True +$ juju run-action client/0 iperf host= [...] +``` + +## Scale out Usage + +With great scalability comes great power, but please don't use this to DDoS anyone without their permission. + +## Known Limitations and Issues + +# Contact Information + +## Contributing to the charm + + - The compiled charm can be found [here](https://www.jujucharms.com/u/nfv/netutils). + - [layer/netutils](https://osm.etsi.org/gitweb/?p=osm/juju-charms.git;a=summary/) contains the source of the layer. + - Please add any bugs or feature requests to the [bugzilla](https://osm.etsi.org/bugzilla/buglist.cgi?component=Juju-charms&list_id=426&product=OSM&resolution=---). diff --git a/juju-charms/layers/netutils/actions.yaml b/juju-charms/layers/netutils/actions.yaml new file mode 100644 index 00000000..f4f78843 --- /dev/null +++ b/juju-charms/layers/netutils/actions.yaml @@ -0,0 +1,133 @@ +nmap: + description: "nmap a thing!" + params: + destination: + description: "destination to scan" + type: string + required: + - destination +ping: + description: 'ping a thing!' + params: + count: + description: "Stop after sending count ECHO_REQUEST packets" + type: integer + default: 30 + destination: + description: "destination of ping request" + type: string + required: + - destination +traceroute: + description: 'trace a thing!' + params: + hops: + description: "Stop tracing after count hops" + type: integer + default: 30 + destination: + description: "destination of traceroute request" + type: string + required: + - destination +dig: + description: "DNS lookup" + params: + nsserver: + description: "The nameserver to lookup against." + type: string + host: + description: "The host to lookup" + type: string + type: + description: "The DNS record type to lookup" + type: string + required: + - host +iperf: + description: "" + params: + host: + description: "" + type: string + port: + description: "" + type: integer + default: 5201 + format: + description: "" + type: string + interval: + description: "" + type: string + affinity: + description: "" + type: string + udp: + description: "Use UDP rather than TCP" + type: boolean + default: False + bandwidth: + description: "Set the target bandwidth to n bits/sec (default 1Mbit/sec for UDP, unlimited for TCP)" + type: integer + default: 1 + time: + description: "Time, in seconds, to transmit for." + type: integer + default: 10 + blockcount: + description: "The number of blocks to transmit" + type: integer + length: + description: "The length of buffer to read or write (default 128KB for TCP, 8KB for UDP)" + type: integer + parallel: + description: "The number of parallel client streams to run" + type: integer + reverse: + description: "Run in reverse mode (server sends, client receives)." + type: boolean + default: false + window: + description: "Window size/socket buffer size." + type: integer + bind: + description: "Bind to a specific interface or multicast address" + type: string + mss: + description: "Set the TCP maximum segment size (MTU - 40 bytes)" + type: integer + no-delay: + description: "Set the TCP no delay, disabling Nagle's algorithm." + type: boolean + default: false + ipv4: + description: "Only use IPv4" + type: boolean + default: false + ipv6: + description: "Only use IPv6" + type: boolean + default: false + tos: + description: "Set the IP 'type of service'" + type: integer + flowlabel: + description: "Set the IPv6 flow label (linux-only)" + type: string + zerocopy: + description: "Use a 'zero copy' method of sending data, such as sendfile(s), instead of the usual write(2)." + type: boolean + default: false + omit: + description: "Omit the first n seconds of the test, to skip past the TCP slow-start period." + type: integer + title: + description: "Prefix every output line with this string." + type: string + congestion: + description: "Set the linux congestion control algorithm." + type: string + + required: + - host diff --git a/juju-charms/layers/netutils/actions/dig b/juju-charms/layers/netutils/actions/dig new file mode 100755 index 00000000..736a4069 --- /dev/null +++ b/juju-charms/layers/netutils/actions/dig @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.dig') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/netutils/actions/iperf b/juju-charms/layers/netutils/actions/iperf new file mode 100755 index 00000000..750028e0 --- /dev/null +++ b/juju-charms/layers/netutils/actions/iperf @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.iperf3') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/netutils/actions/nmap b/juju-charms/layers/netutils/actions/nmap new file mode 100755 index 00000000..ede4f5b9 --- /dev/null +++ b/juju-charms/layers/netutils/actions/nmap @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.nmap') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/netutils/actions/ping b/juju-charms/layers/netutils/actions/ping new file mode 100755 index 00000000..9850fe76 --- /dev/null +++ b/juju-charms/layers/netutils/actions/ping @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.ping') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/netutils/actions/traceroute b/juju-charms/layers/netutils/actions/traceroute new file mode 100755 index 00000000..229ed327 --- /dev/null +++ b/juju-charms/layers/netutils/actions/traceroute @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.traceroute') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/netutils/config.yaml b/juju-charms/layers/netutils/config.yaml new file mode 100644 index 00000000..61010636 --- /dev/null +++ b/juju-charms/layers/netutils/config.yaml @@ -0,0 +1,5 @@ +options: + iperf3: + type: boolean + default: false + description: "Enabling this option will start iperf3 in server mode." diff --git a/juju-charms/layers/netutils/icon.svg b/juju-charms/layers/netutils/icon.svg new file mode 100644 index 00000000..e092eef7 --- /dev/null +++ b/juju-charms/layers/netutils/icon.svg @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/juju-charms/layers/netutils/layer.yaml b/juju-charms/layers/netutils/layer.yaml new file mode 100644 index 00000000..e13c81a8 --- /dev/null +++ b/juju-charms/layers/netutils/layer.yaml @@ -0,0 +1,8 @@ +repo: git@github.com:AdamIsrael/layer-netutils.git +includes: ['layer:basic', 'layer:sshproxy'] +options: + basic: + packages: + - traceroute + - nmap + - iperf3 diff --git a/juju-charms/layers/netutils/metadata.yaml b/juju-charms/layers/netutils/metadata.yaml new file mode 100644 index 00000000..c42d9637 --- /dev/null +++ b/juju-charms/layers/netutils/metadata.yaml @@ -0,0 +1,15 @@ +name: netutils +summary: A suite of network-related utilities. +maintainer: Adam Israel +description: | + A suite of network-related utilities, such as ping and traceroute, that + can be deployed into a data center in order to diagnose connectivity issues. +tags: + # https://jujucharms.com/docs/stable/authors-charm-metadata + - ops + - network + - performance +series: + - trusty + - xenial +subordinate: false diff --git a/juju-charms/layers/netutils/reactive/layer_netutils.py b/juju-charms/layers/netutils/reactive/layer_netutils.py new file mode 100644 index 00000000..1fd4cb24 --- /dev/null +++ b/juju-charms/layers/netutils/reactive/layer_netutils.py @@ -0,0 +1,137 @@ +from charmhelpers.core.hookenv import ( + action_get, + action_fail, + action_set, + config, + log, + status_set, +) + +from charms.reactive import ( + remove_state as remove_flag, + set_state as set_flag, + when, + when_not, +) +import charms.sshproxy +from subprocess import CalledProcessError + + +@when_not('netutils.ready') +def ready(): + status_set('active', 'Ready!') + set_flag('netutils.ready') + + +@when('actions.dig') +def dig(): + err = '' + try: + nsserver = action_get('nsserver') + host = action_get('host') + nstype = action_get('type') + cmd = "dig" + + if nsserver: + cmd += " @{}".format(nsserver) + if host: + cmd += " {}".format(host) + else: + action_fail('Hostname required.') + if nstype: + cmd += " -t {}".format(nstype) + + result, err = charms.sshproxy._run(cmd) + except: + action_fail('dig command failed:' + err) + else: + action_set({'outout': result}) + finally: + remove_flag('actions.dig') + + +@when('actions.nmap') +def nmap(): + err = '' + try: + result, err = charms.sshproxy._run( + 'nmap {}'.format(action_get('destination')) + ) + except: + action_fail('nmap command failed:' + err) + else: + action_set({'outout': result}) + finally: + remove_flag('actions.nmap') + + +@when('actions.ping') +def ping(): + err = '' + try: + result, err = charms.sshproxy._run('ping -qc {} {}'.format( + action_get('count'), action_get('destination')) + ) + + except: + action_fail('ping command failed:' + err) + else: + # Here you can send results back from ping, if you had time to parse it + action_set({'output': result}) + finally: + remove_flag('actions.ping') + + +@when('actions.traceroute') +def traceroute(): + try: + result, err = charms.sshproxy._run( + 'traceroute -m {} {}'.format( + action_get('hops'), + action_get('destination') + ) + ) + except: + action_fail('traceroute command failed') + else: + # Here you can send results back from ping, if you had time to parse it + action_set({'output': result}) + finally: + remove_flag('actions.traceroute') + + +@when('actions.iperf3') +def iperf3(): + err = '' + try: + # TODO: read all the flags via action_get and build the + # proper command line to run iperf3 + host = action_get('host') + + cmd = 'iperf3 -c {} --json'.format(host) + result, err = charms.sshproxy._run(cmd) + except CalledProcessError as e: + action_fail('iperf3 command failed:' + e.output) + else: + action_set({'outout': result}) + finally: + remove_flag('actions.iperf3') + + +@when('config.changed') +def config_changed(): + """ Handle configuration changes """ + cfg = config() + if cfg.changed('iperf3'): + if cfg['iperf3']: + # start iperf in server + daemon mode + cmd = "iperf3 -s -D" + else: + cmd = "killall iperf3" + try: + charms.sshproxy._run(cmd) + log("iperf3 stopped.") + except CalledProcessError: + log("iperf3 not running.") + else: + log("iperf3 started.") diff --git a/juju-charms/layers/netutils/tests/00-setup b/juju-charms/layers/netutils/tests/00-setup new file mode 100755 index 00000000..f0616a56 --- /dev/null +++ b/juju-charms/layers/netutils/tests/00-setup @@ -0,0 +1,5 @@ +#!/bin/bash + +sudo add-apt-repository ppa:juju/stable -y +sudo apt-get update +sudo apt-get install amulet python-requests -y diff --git a/juju-charms/layers/netutils/tests/10-deploy b/juju-charms/layers/netutils/tests/10-deploy new file mode 100755 index 00000000..ef269cda --- /dev/null +++ b/juju-charms/layers/netutils/tests/10-deploy @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +import amulet +import requests +import unittest + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.d = amulet.Deployment() + + self.d.add('layer-netutils') + self.d.expose('layer-netutils') + + self.d.setup(timeout=900) + self.d.sentry.wait() + + self.unit = self.d.sentry['layer-netutils'][0] + + def test_service(self): + # test we can access over http + page = requests.get('http://{}'.format(self.unit.info['public-address'])) + self.assertEqual(page.status_code, 200) + # Now you can use self.d.sentry[SERVICE][UNIT] to address each of the units and perform + # more in-depth steps. Each self.d.sentry[SERVICE][UNIT] has the following methods: + # - .info - An array of the information of that unit from Juju + # - .file(PATH) - Get the details of a file on that unit + # - .file_contents(PATH) - Get plain text output of PATH file from that unit + # - .directory(PATH) - Get details of directory + # - .directory_contents(PATH) - List files and folders in PATH on that unit + # - .relation(relation, service:rel) - Get relation data from return service diff --git a/juju-charms/layers/pingpong/README.md b/juju-charms/layers/pingpong/README.md new file mode 100644 index 00000000..3bec2433 --- /dev/null +++ b/juju-charms/layers/pingpong/README.md @@ -0,0 +1,163 @@ +# Overview + +This repository contains the [Juju] layer that represents a working example of a proxy charm. + +# What is a proxy charm? + +A proxy charm is a limited type of charm that does not interact with software running on the same host, such as controlling and configuring a remote device (a static VM image, a router/switch, etc.). It cannot take advantage of some of Juju's key features, such as [scaling], [relations], and [leadership]. + +Proxy charms are primarily a stop-gap, intended to prototype quickly, with the end goal being to develop it into a full-featured charm, which installs and executes code on the same machine as the charm is running. + +# Usage + +```bash +# Clone this repository +git clone https://osm.etsi.org/gerrit/osm/juju-charms +cd juju-charms + +# Setup environment variables +source juju-env.sh + +cd layers/pingpong +charm build + +# Examine the built charm +cd ../../builds/pingpong +ls +actions config.yaml icon.svg metadata.yaml tests +actions.yaml copyright layer.yaml reactive tox.ini +bin deps lib README.md wheelhouse +builds hooks Makefile requirements.txt + +``` + +You can view a screencast of this: https://asciinema.org/a/96738 + +The `charm build` process combines this pingpong layer with each layer that it +has included in the `metadata.yaml` file, along with their various dependencies. + +This built charm is what will then be used by the SO to communicate with the +VNF. + +# Configuration + +The pingpong charm has several configuration properties that can be set via +the SO: + +- ssh-hostname +- ssh-username +- ssh-password +- ssh-private-key +- mode + +The ssh-* keys are included by the `sshproxy` layer, and enable the charm to +connect to the VNF image. + +The mode key must be one of two values: `ping` or `pong`. This informs the +charm as to which function it is serving. + +# Contact Information +For support, please send an email to the [OSM Tech] list. + + +[OSM Tech]: mailto:OSM_TECH@list.etsi.org +[Juju]: https://jujucharms.com/about +[configure]: https://jujucharms.com/docs/2.0/charms-config +[scaling]: https://jujucharms.com/docs/2.0/charms-scaling +[relations]: https://jujucharms.com/docs/2.0/charms-relations +[leadership]: https://jujucharms.com/docs/2.0/developer-leadership +[created your charm]: https://jujucharms.com/docs/2.0/developer-getting-started + + + + + +----- + + +# Integration + +After you've [created your charm], open `interfaces.yaml` and add +`layer:sshproxy` to the includes stanza, as shown below: +``` +includes: ['layer:basic', 'layer:sshproxy'] +``` + +## Reactive states + +This layer will set the following states: + +- `sshproxy.configured` This state is set when SSH credentials have been supplied to the charm. + + +## Example +In `reactive/mycharm.py`, you can add logic to execute commands over SSH. This +example is run via a `start` action, and starts a service running on a remote +host. +``` +... +import charms.sshproxy + + +@when('sshproxy.configured') +@when('actions.start') +def start(): + """ Execute's the command, via the start action` using the + configured SSH credentials + """ + sshproxy.ssh("service myservice start") + +``` + +## Actions +This layer includes a built-in `run` action useful for debugging or running arbitrary commands: + +``` +$ juju run-action mycharm/0 run command=hostname +Action queued with id: 014b72f3-bc02-4ecb-8d38-72bce03bbb63 + +$ juju show-action-output 014b72f3-bc02-4ecb-8d38-72bce03bbb63 +results: + output: juju-66a5f3-11 +status: completed +timing: + completed: 2016-10-27 19:53:49 +0000 UTC + enqueued: 2016-10-27 19:53:44 +0000 UTC + started: 2016-10-27 19:53:48 +0000 UTC + +``` +## Known Limitations and Issues + +### Security issues + +- Password and key-based authentications are supported, with the caveat that +both (password and private key) are stored plaintext within the Juju controller. + +# Configuration and Usage + +This layer adds the following configuration options: +- ssh-hostname +- ssh-username +- ssh-password +- ssh-private-key + +Once [configure] those values at any time. Once they are set, the `sshproxy.configured` state flag will be toggled: + +``` +juju deploy mycharm ssh-hostname=10.10.10.10 ssh-username=ubuntu ssh-password=yourpassword +``` +or +``` +juju deploy mycharm ssh-hostname=10.10.10.10 ssh-username=ubuntu ssh-private-key="cat `~/.ssh/id_rsa`" +``` + + +# Contact Information +Homepage: https://github.com/AdamIsrael/layer-sshproxy + +[Juju]: https://jujucharms.com/about +[configure]: https://jujucharms.com/docs/2.0/charms-config +[scaling]: https://jujucharms.com/docs/2.0/charms-scaling +[relations]: https://jujucharms.com/docs/2.0/charms-relations +[leadership]: https://jujucharms.com/docs/2.0/developer-leadership +[created your charm]: https://jujucharms.com/docs/2.0/developer-getting-started diff --git a/juju-charms/layers/pingpong/actions.yaml b/juju-charms/layers/pingpong/actions.yaml new file mode 100644 index 00000000..a5928f1f --- /dev/null +++ b/juju-charms/layers/pingpong/actions.yaml @@ -0,0 +1,32 @@ +set-server: + description: "Set the target IP address and port" + params: + server-ip: + description: "IP on which the target service is listening." + type: string + default: "" + server-port: + description: "Port on which the target service is listening." + type: integer + default: 5555 + required: + - server-ip +set-rate: + description: "Set the rate of packet generation." + params: + rate: + description: "Packet rate." + type: integer + default: 5 +get-stats: + description: "Get the stats." +get-state: + description: "Get the admin state of the target service." +get-rate: + description: "Get the rate set on the target service." +get-server: + description: "Get the target server and IP set" +start-traffic: + description: "Start the traffic generator or echo." +stop-traffic: + description: "Stop the traffic generator or echo." diff --git a/juju-charms/layers/pingpong/actions/get-rate b/juju-charms/layers/pingpong/actions/get-rate new file mode 100755 index 00000000..959b3e93 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/get-rate @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.get-rate') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/get-server b/juju-charms/layers/pingpong/actions/get-server new file mode 100755 index 00000000..52e00894 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/get-server @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.get-server') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/get-state b/juju-charms/layers/pingpong/actions/get-state new file mode 100755 index 00000000..446e8d71 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/get-state @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.get-state') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/get-stats b/juju-charms/layers/pingpong/actions/get-stats new file mode 100755 index 00000000..086afc27 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/get-stats @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.get-stats') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/set-rate b/juju-charms/layers/pingpong/actions/set-rate new file mode 100755 index 00000000..8fb723ef --- /dev/null +++ b/juju-charms/layers/pingpong/actions/set-rate @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.set-rate') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/set-server b/juju-charms/layers/pingpong/actions/set-server new file mode 100755 index 00000000..d1e908f5 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/set-server @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.set-server') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/start-traffic b/juju-charms/layers/pingpong/actions/start-traffic new file mode 100755 index 00000000..562ac4c7 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/start-traffic @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.start-traffic') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/actions/stop-traffic b/juju-charms/layers/pingpong/actions/stop-traffic new file mode 100755 index 00000000..9352b331 --- /dev/null +++ b/juju-charms/layers/pingpong/actions/stop-traffic @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('lib') + +from charms.reactive import main +from charms.reactive import set_state +from charmhelpers.core.hookenv import action_fail + +""" +`set_state` only works here because it's flushed to disk inside the `main()` +loop. remove_state will need to be called inside the action method. +""" +set_state('actions.stop-traffic') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/juju-charms/layers/pingpong/config.yaml b/juju-charms/layers/pingpong/config.yaml new file mode 100644 index 00000000..437524e4 --- /dev/null +++ b/juju-charms/layers/pingpong/config.yaml @@ -0,0 +1,5 @@ +options: + mode: + type: string + default: + description: "The service type: [ping, pong]" diff --git a/juju-charms/layers/pingpong/icon.svg b/juju-charms/layers/pingpong/icon.svg new file mode 100644 index 00000000..e092eef7 --- /dev/null +++ b/juju-charms/layers/pingpong/icon.svg @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/juju-charms/layers/pingpong/layer.yaml b/juju-charms/layers/pingpong/layer.yaml new file mode 100644 index 00000000..833eea68 --- /dev/null +++ b/juju-charms/layers/pingpong/layer.yaml @@ -0,0 +1,4 @@ +includes: + - layer:basic + - layer:vnfproxy +repo: https://osm.etsi.org/gerrit/osm/juju-charms diff --git a/juju-charms/layers/pingpong/metadata.yaml b/juju-charms/layers/pingpong/metadata.yaml new file mode 100644 index 00000000..1840743e --- /dev/null +++ b/juju-charms/layers/pingpong/metadata.yaml @@ -0,0 +1,13 @@ +name: pingpong +summary: +maintainer: Adam Israel +description: | + +tags: + # Replace "misc" with one or more whitelisted tags from this list: + # https://jujucharms.com/docs/stable/authors-charm-metadata + - misc +subordinate: false +series: + - trusty + - xenial diff --git a/juju-charms/layers/pingpong/reactive/pingpong.py b/juju-charms/layers/pingpong/reactive/pingpong.py new file mode 100644 index 00000000..b5a5db96 --- /dev/null +++ b/juju-charms/layers/pingpong/reactive/pingpong.py @@ -0,0 +1,272 @@ +from charmhelpers.core.hookenv import ( + action_get, + action_fail, + action_set, + config, + status_set, +) + +from charms.reactive import ( + remove_state as remove_flag, + set_state as set_flag, + when, +) +import charms.sshproxy +from subprocess import ( + Popen, + CalledProcessError, + PIPE, +) + + +cfg = config() + + +@when('config.changed') +def config_changed(): + if all(k in cfg for k in ['mode']): + if cfg['mode'] in ['ping', 'pong']: + set_flag('pingpong.configured') + status_set('active', 'ready!') + return + status_set('blocked', 'Waiting for configuration') + + +def is_ping(): + if cfg['mode'] == 'ping': + return True + return False + + +def is_pong(): + return not is_ping() + + +def get_port(): + port = 18888 + if is_pong(): + port = 18889 + return port + + +@when('pingpong.configured') +@when('actions.start') +def start(): + try: + # Bring up the eth1 interface. + # The selinux label on the file needs to be set correctly + cmd = "sudo timeout 5 /sbin/restorecon -v /etc/sysconfig/network-scripts/ifcfg-eth1" + result, err = charms.sshproxy._run(cmd) + except Exception as e: + err = "{}".format(e) + action_fail('command failed: {}, errors: {}'.format(err, e.output)) + remove_flag('actions.start') + return + + try: + cmd = "sudo timeout 30 /sbin/ifup eth1" + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + remove_flag('actions.start') + return + + try: + cmd = "sudo timeout 30 /usr/bin/systemctl start {}". \ + format(cfg['mode']) + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.start') + + +@when('pingpong.configured') +@when('actions.stop') +def stop(): + try: + # Enter the command to stop your service(s) + cmd = "sudo timeout 30 /usr/bin/systemctl stop {}".format(cfg['mode']) + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.stop') + + +@when('pingpong.configured') +@when('actions.restart') +def restart(): + try: + # Enter the command to restart your service(s) + cmd = "sudo timeout 30 /usr/bin/systemctl restart {}".format(cfg['mode']) + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.restart') + + +@when('pingpong.configured') +@when('actions.set-server') +def set_server(): + try: + # Get the target service info + target_ip = action_get('server-ip') + target_port = action_get('server-port') + + data = '{{"ip" : "{}", "port" : {} }}'. \ + format(target_ip, target_port) + + cmd = format_curl( + 'POST', + '/server', + data, + ) + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.set-server') + + +@when('pingpong.configured') +@when('actions.set-rate') +def set_rate(): + try: + if is_ping(): + rate = action_get('rate') + cmd = format_curl('POST', '/rate', '{{"rate" : {}}}'.format(rate)) + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + err = "{}".format(e) + action_fail('command failed: {}, errors: {}'.format(err, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.set-rate') + + +@when('pingpong.configured') +@when('actions.get-rate') +def get_rate(): + try: + if is_ping(): + cmd = format_curl('GET', '/rate') + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.get-rate') + + +@when('pingpong.configured') +@when('actions.get-state') +def get_state(): + try: + cmd = format_curl('GET', '/state') + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.get-state') + + +@when('pingpong.configured') +@when('actions.get-stats') +def get_stats(): + try: + cmd = format_curl('GET', '/stats') + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.get-stats') + + +@when('pingpong.configured') +@when('actions.start-traffic') +def start_traffic(): + try: + cmd = format_curl('POST', '/adminstatus/state', '{"enable" : true}') + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.start-traffic') + + +@when('pingpong.configured') +@when('actions.stop-traffic') +def stop_traffic(): + try: + cmd = format_curl('POST', '/adminstatus/state', '{"enable" : false}') + + result, err = charms.sshproxy._run(cmd) + except Exception as e: + action_fail('command failed: {}, errors: {}'.format(e, e.output)) + else: + action_set({'stdout': result, + 'errors': err}) + finally: + remove_flag('actions.stop-traffic') + + +def format_curl(method, path, data=None): + """ A utility function to build the curl command line. """ + + # method must be GET or POST + if method not in ['GET', 'POST']: + # Throw exception + return None + + # Get our service info + host = '127.0.0.1' + port = get_port() + mode = cfg['mode'] + + cmd = ['curl', + # '-D', '/dev/stdout', + '-H', 'Accept: application/vnd.yang.data+xml', + '-H', 'Content-Type: application/vnd.yang.data+json', + '-X', method] + + if method == "POST" and data: + cmd.append('-d') + cmd.append('{}'.format(data)) + + cmd.append( + 'http://{}:{}/api/v1/{}{}'.format(host, port, mode, path) + ) + return cmd diff --git a/juju-charms/layers/pingpong/tests/00-setup b/juju-charms/layers/pingpong/tests/00-setup new file mode 100755 index 00000000..f0616a56 --- /dev/null +++ b/juju-charms/layers/pingpong/tests/00-setup @@ -0,0 +1,5 @@ +#!/bin/bash + +sudo add-apt-repository ppa:juju/stable -y +sudo apt-get update +sudo apt-get install amulet python-requests -y diff --git a/juju-charms/layers/pingpong/tests/10-deploy b/juju-charms/layers/pingpong/tests/10-deploy new file mode 100755 index 00000000..d1d4719d --- /dev/null +++ b/juju-charms/layers/pingpong/tests/10-deploy @@ -0,0 +1,35 @@ +#!/usr/bin/python3 + +import amulet +import requests +import unittest + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.d = amulet.Deployment() + + self.d.add('pingpong') + self.d.expose('pingpong') + + self.d.setup(timeout=900) + self.d.sentry.wait() + + self.unit = self.d.sentry['pingpong'][0] + + def test_service(self): + # test we can access over http + page = requests.get('http://{}'.format(self.unit.info['public-address'])) + self.assertEqual(page.status_code, 200) + # Now you can use self.d.sentry[SERVICE][UNIT] to address each of the units and perform + # more in-depth steps. Each self.d.sentry[SERVICE][UNIT] has the following methods: + # - .info - An array of the information of that unit from Juju + # - .file(PATH) - Get the details of a file on that unit + # - .file_contents(PATH) - Get plain text output of PATH file from that unit + # - .directory(PATH) - Get details of directory + # - .directory_contents(PATH) - List files and folders in PATH on that unit + # - .relation(relation, service:rel) - Get relation data from return service + + +if __name__ == '__main__': + unittest.main() diff --git a/juju-charms/layers/vyos-proxy/Makefile b/juju-charms/layers/vyos-proxy/Makefile new file mode 100644 index 00000000..a1ad3a5c --- /dev/null +++ b/juju-charms/layers/vyos-proxy/Makefile @@ -0,0 +1,24 @@ +#!/usr/bin/make + +all: lint unit_test + + +.PHONY: clean +clean: + @rm -rf .tox + +.PHONY: apt_prereqs +apt_prereqs: + @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip) + @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox) + +.PHONY: lint +lint: apt_prereqs + @tox --notest + @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests) + @charm proof + +.PHONY: unit_test +unit_test: apt_prereqs + @echo Starting tests... + tox diff --git a/juju-charms/layers/vyos-proxy/README.md b/juju-charms/layers/vyos-proxy/README.md new file mode 100644 index 00000000..0337c83b --- /dev/null +++ b/juju-charms/layers/vyos-proxy/README.md @@ -0,0 +1,221 @@ +# Overview + +This is the base layer for all charms [built using layers][building]. It +provides all of the standard Juju hooks and runs the +[charms.reactive.main][charms.reactive] loop for them. It also bootstraps the +[charm-helpers][] and [charms.reactive][] libraries and all of their +dependencies for use by the charm. + +# Usage + +To create a charm layer using this base layer, you need only include it in +a `layer.yaml` file: + +```yaml +includes: ['layer:basic'] +``` + +This will fetch this layer from [interfaces.juju.solutions][] and incorporate +it into your charm layer. You can then add handlers under the `reactive/` +directory. Note that **any** file under `reactive/` will be expected to +contain handlers, whether as Python decorated functions or [executables][non-python] +using the [external handler protocol][]. + +### Charm Dependencies + +Each layer can include a `wheelhouse.txt` file with Python requirement lines. +For example, this layer's `wheelhouse.txt` includes: + +``` +pip>=7.0.0,<8.0.0 +charmhelpers>=0.4.0,<1.0.0 +charms.reactive>=0.1.0,<2.0.0 +``` + +All of these dependencies from each layer will be fetched (and updated) at build +time and will be automatically installed by this base layer before any reactive +handlers are run. + +Note that the `wheelhouse.txt` file is intended for **charm** dependencies only. +That is, for libraries that the charm code itself needs to do its job of deploying +and configuring the payload. If the payload itself has Python dependencies, those +should be handled separately, by the charm. + +See [PyPI][pypi charms.X] for packages under the `charms.` namespace which might +be useful for your charm. + +### Layer Namespace + +Each layer has a reserved section in the `charms.layer.` Python package namespace, +which it can populate by including a `lib/charms/layer/.py` file or +by placing files under `lib/charms/layer//`. (If the layer name +includes hyphens, replace them with underscores.) These can be helpers that the +layer uses internally, or it can expose classes or functions to be used by other +layers to interact with that layer. + +For example, a layer named `foo` could include a `lib/charms/layer/foo.py` file +with some helper functions that other layers could access using: + +```python +from charms.layer.foo import my_helper +``` + +### Layer Options + +Any layer can define options in its `layer.yaml`. Those options can then be set +by other layers to change the behavior of your layer. The options are defined +using [jsonschema][], which is the same way that [action paramters][] are defined. + +For example, the `foo` layer could include the following option definitons: + +```yaml +includes: ['layer:basic'] +defines: # define some options for this layer (the layer "foo") + enable-bar: # define an "enable-bar" option for this layer + description: If true, enable support for "bar". + type: boolean + default: false +``` + +A layer using `foo` could then set it: + +```yaml +includes: ['layer:foo'] +options: + foo: # setting options for the "foo" layer + enable-bar: true # set the "enable-bar" option to true +``` + +The `foo` layer can then use the `charms.layer.options` helper to load the values +for the options that it defined. For example: + +```python +from charms import layer + +@when('state') +def do_thing(): + layer_opts = layer.options('foo') # load all of the options for the "foo" layer + if layer_opts['enable-bar']: # check the value of the "enable-bar" option + hookenv.log("Bar is enabled") +``` + +You can also access layer options in other handlers, such as Bash, using +the command-line interface: + +```bash +. charms.reactive.sh + +@when 'state' +function do_thing() { + if layer_option foo enable-bar; then + juju-log "Bar is enabled" + juju-log "bar-value is: $(layer_option foo bar-value)" + fi +} + +reactive_handler_main +``` + +Note that options of type `boolean` will set the exit code, while other types +will be printed out. + +# Hooks + +This layer provides hooks that other layers can react to using the decorators +of the [charms.reactive][] library: + + * `config-changed` + * `install` + * `leader-elected` + * `leader-settings-changed` + * `start` + * `stop` + * `upgrade-charm` + * `update-status` + +Other hooks are not implemented at this time. A new layer can implement storage +or relation hooks in their own layer by putting them in the `hooks` directory. + +**Note:** Because `update-status` is invoked every 5 minutes, you should take +care to ensure that your reactive handlers only invoke expensive operations +when absolutely necessary. It is recommended that you use helpers like +[`@only_once`][], [`@when_file_changed`][], and [`data_changed`][] to ensure +that handlers run only when necessary. + +# Layer Configuration + +This layer supports the following options, which can be set in `layer.yaml`: + + * **packages** A list of system packages to be installed before the reactive + handlers are invoked. + + * **use_venv** If set to true, the charm dependencies from the various + layers' `wheelhouse.txt` files will be installed in a Python virtualenv + located at `$CHARM_DIR/../.venv`. This keeps charm dependencies from + conflicting with payload dependencies, but you must take care to preserve + the environment and interpreter if using `execl` or `subprocess`. + + * **include_system_packages** If set to true and using a venv, include + the `--system-site-packages` options to make system Python libraries + visible within the venv. + +An example `layer.yaml` using these options might be: + +```yaml +includes: ['layer:basic'] +options: + basic: + packages: ['git'] + use_venv: true + include_system_packages: true +``` + + +# Reactive States + +This layer will set the following states: + + * **`config.changed`** Any config option has changed from its previous value. + This state is cleared automatically at the end of each hook invocation. + + * **`config.changed.