From: Jokin Garay Date: Thu, 2 Feb 2017 13:25:28 +0000 (+0100) Subject: FlowNAC charms (plugtest) X-Git-Tag: v1.1.0~2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=3d1ebf729a84cbbc411c31e868a60391e5e5c0f6;p=osm%2Fjuju-charms.git FlowNAC charms (plugtest) Change-Id: Iaa95be797e912c4bb8de8fa9f4bf33a9e15ed749 Signed-off-by: Jokin Garay --- diff --git a/Makefile b/Makefile index bf2b4c9..d1dfd84 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ BUILD_DIR = . -CHARMS:= vpe-router vyos-proxy pingpong +CHARMS:= vpe-router vyos-proxy pingpong flownac CHARM_SRC_DIR := layers CHARM_BUILD_DIR := $(BUILD_DIR)/builds @@ -31,4 +31,3 @@ clean: $(CHARM_BUILD_DIR)/%: $(CHARM_SRC_DIR)/% charm-build -o $(BUILD_DIR) $< - diff --git a/layers/flownac/README.md b/layers/flownac/README.md new file mode 100644 index 0000000..3bec243 --- /dev/null +++ b/layers/flownac/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/layers/flownac/actions.yaml b/layers/flownac/actions.yaml new file mode 100644 index 0000000..dbe038a --- /dev/null +++ b/layers/flownac/actions.yaml @@ -0,0 +1,10 @@ +start: + description: "Start the VNF service." +stop: + description: "Stop the VNF service." +restart: + description: "Restart the VNF service." +check-serv: + description: "Check service is reachable." +start-client: + description: "Start demo client." diff --git a/layers/flownac/actions/check-serv b/layers/flownac/actions/check-serv new file mode 100755 index 0000000..b08ade5 --- /dev/null +++ b/layers/flownac/actions/check-serv @@ -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.check-serv') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/layers/flownac/actions/restart b/layers/flownac/actions/restart new file mode 100755 index 0000000..6eb06b8 --- /dev/null +++ b/layers/flownac/actions/restart @@ -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.restart') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/layers/flownac/actions/start b/layers/flownac/actions/start new file mode 100755 index 0000000..f8b65dc --- /dev/null +++ b/layers/flownac/actions/start @@ -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') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/layers/flownac/actions/start-client b/layers/flownac/actions/start-client new file mode 100755 index 0000000..97ed016 --- /dev/null +++ b/layers/flownac/actions/start-client @@ -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-client') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/layers/flownac/actions/stop b/layers/flownac/actions/stop new file mode 100755 index 0000000..6416be1 --- /dev/null +++ b/layers/flownac/actions/stop @@ -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') + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/layers/flownac/config.yaml b/layers/flownac/config.yaml new file mode 100644 index 0000000..587373f --- /dev/null +++ b/layers/flownac/config.yaml @@ -0,0 +1,5 @@ +options: + mode: + type: string + default: + description: "VNF type: [fne, fnc, fnu, fnd]" diff --git a/layers/flownac/icon.svg b/layers/flownac/icon.svg new file mode 100644 index 0000000..e092eef --- /dev/null +++ b/layers/flownac/icon.svg @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/layers/flownac/layer.yaml b/layers/flownac/layer.yaml new file mode 100644 index 0000000..1ee4b75 --- /dev/null +++ b/layers/flownac/layer.yaml @@ -0,0 +1,3 @@ +includes: + - layer:basic + - layer:sshproxy diff --git a/layers/flownac/metadata.yaml b/layers/flownac/metadata.yaml new file mode 100644 index 0000000..bd1496e --- /dev/null +++ b/layers/flownac/metadata.yaml @@ -0,0 +1,15 @@ +name: flownac +summary: Charms for the FlowNAC NS +maintainer: Jokin Garay +description: | + Charms for the FlowNAC NS +tags: + # Replace "misc" with one or more whitelisted tags from this list: + # https://jujucharms.com/docs/stable/authors-charm-metadata + - security + - vnf + - osm +subordinate: false +series: + - trusty + - xenial diff --git a/layers/flownac/reactive/flownac.py b/layers/flownac/reactive/flownac.py new file mode 100644 index 0000000..ca31bff --- /dev/null +++ b/layers/flownac/reactive/flownac.py @@ -0,0 +1,129 @@ +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 + +cfg = config() + + +@when('config.changed') +def config_changed(): + if all(k in cfg for k in ['mode']): + if cfg['mode'] in ['fnc', 'fne', 'fnd', 'fnu']: + print("Configuring: FlowNAC", cfg['mode'].upper()) + set_flag('flownac.configured') + status_set('active', 'VNF ready!') + return + +def is_fnc(): + if cfg['mode'] == 'fnc': + return True + return False + +def is_fne(): + if cfg['mode'] == 'fne': + return True + return False + +def is_fnd(): + if cfg['mode'] == 'fnd': + return True + return False + +def is_fnu(): + if cfg['mode'] == 'fnu': + return True + return False + + +@when('flownac.configured') +@when('actions.start') +def start(): + err = '' + try: + cmd = "sudo systemctl start flownac.service" + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'stdout': result}) + finally: + remove_flag('actions.start') + set_flag('flownac.started') + status_set('active', 'VNF started!') + + +@when('flownac.configured') +@when('actions.stop') +def stop(): + err = '' + try: + cmd = "sudo systemctl stop flownac.service" + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'stdout': result}) + finally: + remove_flag('actions.stop') + remove_flag('flownac.started') + status_set('active', 'VNF stopped!') + + +@when('flownac.configured') +@when('actions.restart') +def restart(): + err = '' + try: + cmd = "sudo systemctl restart flownac.service" + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'stdout': result}) + finally: + remove_flag('actions.restart') + set_flag('flownac.started') + status_set('active', 'VNF restarted!') + +@when('flownac.configured') +@when('actions.check-serv') +def check_serv(): + err = '' + try: + #if is_fnu(): + cmd = "./check.sh" + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'stdout': result}) + finally: + remove_flag('actions.check-serv') + +@when('flownac.configured') +@when('actions.start-client') +def start_client(): + err = '' + try: + #if is_fnu(): + # Client not prepared for charm execution + #cmd = "./client.sh" + cmd = "ls" + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'stdout': result}) + finally: + remove_flag('actions.start-client') diff --git a/layers/flownac/tests/00-setup b/layers/flownac/tests/00-setup new file mode 100755 index 0000000..f0616a5 --- /dev/null +++ b/layers/flownac/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/layers/flownac/tests/10-deploy b/layers/flownac/tests/10-deploy new file mode 100755 index 0000000..d1d4719 --- /dev/null +++ b/layers/flownac/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()