From bf79352ca652b228c5c216564cc512b635e3c5e4 Mon Sep 17 00:00:00 2001 From: Adam Israel Date: Tue, 20 Nov 2018 13:54:13 -0500 Subject: [PATCH] [bug 581] Fix parameter checking if no data-type Fix bug #581, which reported that the deployment of the simplecharm failed because the filename parameter was invalid. This turned out to be an issue introduced with the parameter type-checking added for R5. This has been fixed, and an integration test has been added that exercises the simplecharm example. Signed-off-by: Adam Israel --- n2vc/vnf.py | 7 + tests/charms/layers/simple/README.md | 53 ++++ tests/charms/layers/simple/actions.yaml | 9 + tests/charms/layers/simple/actions/touch | 33 +++ tests/charms/layers/simple/config.yaml | 14 + tests/charms/layers/simple/icon.svg | 279 ++++++++++++++++++ tests/charms/layers/simple/layer.yaml | 4 + tests/charms/layers/simple/metadata.yaml | 5 + tests/charms/layers/simple/metrics.yaml | 5 + tests/charms/layers/simple/reactive/simple.py | 44 +++ tests/charms/layers/simple/tests/00-setup | 5 + tests/charms/layers/simple/tests/10-deploy | 35 +++ tests/integration/test_simplecharm.py | 178 +++++++++++ 13 files changed, 671 insertions(+) create mode 100644 tests/charms/layers/simple/README.md create mode 100644 tests/charms/layers/simple/actions.yaml create mode 100755 tests/charms/layers/simple/actions/touch create mode 100644 tests/charms/layers/simple/config.yaml create mode 100644 tests/charms/layers/simple/icon.svg create mode 100644 tests/charms/layers/simple/layer.yaml create mode 100644 tests/charms/layers/simple/metadata.yaml create mode 100644 tests/charms/layers/simple/metrics.yaml create mode 100644 tests/charms/layers/simple/reactive/simple.py create mode 100755 tests/charms/layers/simple/tests/00-setup create mode 100755 tests/charms/layers/simple/tests/10-deploy create mode 100644 tests/integration/test_simplecharm.py diff --git a/n2vc/vnf.py b/n2vc/vnf.py index 06f1ff6..1c79aed 100644 --- a/n2vc/vnf.py +++ b/n2vc/vnf.py @@ -887,6 +887,10 @@ class N2VC: param = str(parameter['name']) value = None + # If there's no value, use the default-value (if set) + if parameter['value'] is None and 'default-value' in parameter: + value = parameter['default-value'] + # Typecast parameter value, if present if 'data-type' in parameter: paramtype = str(parameter['data-type']).lower() @@ -897,6 +901,9 @@ class N2VC: value = bool(parameter['value']) else: value = str(parameter['value']) + else: + # If there's no data-type, assume the value is a string + value = str(parameter['value']) if parameter['value'] == "": params[param] = str(values[parameter['value']]) diff --git a/tests/charms/layers/simple/README.md b/tests/charms/layers/simple/README.md new file mode 100644 index 0000000..f9d6eed --- /dev/null +++ b/tests/charms/layers/simple/README.md @@ -0,0 +1,53 @@ +# Overview + +This is an example charm as demonstrated in the OSM [Hackfest](https://osm.etsi.org/wikipub/index.php/OSM_workshops_and_events) series. + +This is intended to provide a well-documented example of the proxy charm written by Hackfest participants. + +# Prerequisites + +There are two ways that you can exercise this charm: install the latest stable release of OSM or use Juju directly. + +The workshop materials and tutorials cover using charms as part of OSM. You can follow that approach, but this README will focus on using Juju directly. We highly recommend that vendors and charm developers use this approach for the initial development of the charm. + +## Ubuntu 16.04 or higher + +We recommend using Ubuntu 16.04 or higher for the development and testing of charms. It is assumed that you have installed Ubuntu either on physical hardware or in a Virtual Machine. + +## Install LXD and Juju + +We will be installing the required software via snap. Snaps are containerised software packages, preferred because they are easy to create and install, will automatically update to the latest stable version, and contain bundled dependencies. + +``` +snap install lxd +snap install juju +snap install charm +``` + +# Usage + + +## Known Limitations and Issues + +This not only helps users but gives people a place to start if they want to help +you add features to your charm. + +# Configuration + +The configuration options will be listed on the charm store, however If you're +making assumptions or opinionated decisions in the charm (like setting a default +administrator password), you should detail that here so the user knows how to +change it immediately, etc. + +# Contact Information + +## Upstream Project Name + + - Upstream website + - Upstream bug tracker + - Upstream mailing list or contact information + - Feel free to add things if it's useful for users + + +[service]: http://example.com +[icon guidelines]: https://jujucharms.com/docs/stable/authors-charm-icon diff --git a/tests/charms/layers/simple/actions.yaml b/tests/charms/layers/simple/actions.yaml new file mode 100644 index 0000000..6cd6f8c --- /dev/null +++ b/tests/charms/layers/simple/actions.yaml @@ -0,0 +1,9 @@ +touch: + description: "Touch a file on the VNF." + params: + filename: + description: "The name of the file to touch." + type: string + default: "" + required: + - filename diff --git a/tests/charms/layers/simple/actions/touch b/tests/charms/layers/simple/actions/touch new file mode 100755 index 0000000..7e30af4 --- /dev/null +++ b/tests/charms/layers/simple/actions/touch @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +## +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +## +import sys +sys.path.append('lib') + +from charms.reactive import main, set_flag +from charmhelpers.core.hookenv import action_fail, action_name + +""" +`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_flag('actions.{}'.format(action_name())) + +try: + main() +except Exception as e: + action_fail(repr(e)) diff --git a/tests/charms/layers/simple/config.yaml b/tests/charms/layers/simple/config.yaml new file mode 100644 index 0000000..51f2ce4 --- /dev/null +++ b/tests/charms/layers/simple/config.yaml @@ -0,0 +1,14 @@ +options: + string-option: + type: string + default: "Default Value" + description: "A short description of the configuration option" + boolean-option: + type: boolean + default: False + description: "A short description of the configuration option" + int-option: + type: int + default: 9001 + description: "A short description of the configuration option" + diff --git a/tests/charms/layers/simple/icon.svg b/tests/charms/layers/simple/icon.svg new file mode 100644 index 0000000..e092eef --- /dev/null +++ b/tests/charms/layers/simple/icon.svg @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/tests/charms/layers/simple/layer.yaml b/tests/charms/layers/simple/layer.yaml new file mode 100644 index 0000000..3fed5e2 --- /dev/null +++ b/tests/charms/layers/simple/layer.yaml @@ -0,0 +1,4 @@ +includes: ['layer:basic', 'layer:vnfproxy'] +options: + basic: + use_venv: false diff --git a/tests/charms/layers/simple/metadata.yaml b/tests/charms/layers/simple/metadata.yaml new file mode 100644 index 0000000..fd80d1a --- /dev/null +++ b/tests/charms/layers/simple/metadata.yaml @@ -0,0 +1,5 @@ +name: simple +summary: A simple VNF proxy charm +maintainer: Adam Israel +subordinate: false +series: ['xenial'] diff --git a/tests/charms/layers/simple/metrics.yaml b/tests/charms/layers/simple/metrics.yaml new file mode 100644 index 0000000..6ebb605 --- /dev/null +++ b/tests/charms/layers/simple/metrics.yaml @@ -0,0 +1,5 @@ +metrics: + uptime: + type: gauge + description: "Uptime of the VNF" + command: awk '{print $1}' /proc/uptime diff --git a/tests/charms/layers/simple/reactive/simple.py b/tests/charms/layers/simple/reactive/simple.py new file mode 100644 index 0000000..228be3c --- /dev/null +++ b/tests/charms/layers/simple/reactive/simple.py @@ -0,0 +1,44 @@ +from charmhelpers.core.hookenv import ( + action_get, + action_fail, + action_set, + status_set, +) +from charms.reactive import ( + clear_flag, + set_flag, + when, + when_not, +) +import charms.sshproxy + + +@when('sshproxy.configured') +@when_not('simple.installed') +def install_simple_proxy_charm(): + """Post-install actions. + + This function will run when two conditions are met: + 1. The 'sshproxy.configured' state is set + 2. The 'simple.installed' state is not set + + This ensures that the workload status is set to active only when the SSH + proxy is properly configured. + """ + set_flag('simple.installed') + status_set('active', 'Ready!') + + +@when('actions.touch') +def touch(): + err = '' + try: + filename = action_get('filename') + cmd = ['touch {}'.format(filename)] + result, err = charms.sshproxy._run(cmd) + except: + action_fail('command failed:' + err) + else: + action_set({'output': result}) + finally: + clear_flag('actions.touch') diff --git a/tests/charms/layers/simple/tests/00-setup b/tests/charms/layers/simple/tests/00-setup new file mode 100755 index 0000000..f0616a5 --- /dev/null +++ b/tests/charms/layers/simple/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/tests/charms/layers/simple/tests/10-deploy b/tests/charms/layers/simple/tests/10-deploy new file mode 100755 index 0000000..9a26117 --- /dev/null +++ b/tests/charms/layers/simple/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('simple') + self.d.expose('simple') + + self.d.setup(timeout=900) + self.d.sentry.wait() + + self.unit = self.d.sentry['simple'][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/tests/integration/test_simplecharm.py b/tests/integration/test_simplecharm.py new file mode 100644 index 0000000..7f4cafd --- /dev/null +++ b/tests/integration/test_simplecharm.py @@ -0,0 +1,178 @@ +""" +Exercise the simplecharm hackfest example: +https://osm-download.etsi.org/ftp/osm-4.0-four/4th-hackfest/packages/hackfest_simplecharm_vnf.tar.gz +""" + +import asyncio +import logging +import pytest +from .. import base + + +# @pytest.mark.serial +class TestCharm(base.TestN2VC): + + NSD_YAML = """ + nsd:nsd-catalog: + nsd: + - id: charmproxy-ns + name: charmproxy-ns + short-name: charmproxy-ns + description: NS with 1 VNF connected by datanet and mgmtnet VLs + version: '1.0' + logo: osm.png + constituent-vnfd: + - vnfd-id-ref: charmproxy-vnf + member-vnf-index: '1' + vld: + - id: mgmtnet + name: mgmtnet + short-name: mgmtnet + type: ELAN + mgmt-network: 'true' + vim-network-name: mgmt + vnfd-connection-point-ref: + - vnfd-id-ref: charmproxy-vnf + member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-mgmt + - vnfd-id-ref: charmproxy-vnf + member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-mgmt + - id: datanet + name: datanet + short-name: datanet + type: ELAN + vnfd-connection-point-ref: + - vnfd-id-ref: charmproxy-vnf + member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-data + - vnfd-id-ref: charmproxy-vnf + member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-data + """ + + VNFD_YAML = """ + vnfd:vnfd-catalog: + vnfd: + - id: hackfest-simplecharm-vnf + name: hackfest-simplecharm-vnf + short-name: hackfest-simplecharm-vnf + version: '1.0' + description: A VNF consisting of 2 VDUs connected to an internal VL, and one VDU with cloud-init + logo: osm.png + connection-point: + - id: vnf-mgmt + name: vnf-mgmt + short-name: vnf-mgmt + type: VPORT + - id: vnf-data + name: vnf-data + short-name: vnf-data + type: VPORT + mgmt-interface: + cp: vnf-mgmt + internal-vld: + - id: internal + name: internal + short-name: internal + type: ELAN + internal-connection-point: + - id-ref: mgmtVM-internal + - id-ref: dataVM-internal + vdu: + - id: mgmtVM + name: mgmtVM + image: hackfest3-mgmt + count: '1' + vm-flavor: + vcpu-count: '1' + memory-mb: '1024' + storage-gb: '10' + interface: + - name: mgmtVM-eth0 + position: '1' + type: EXTERNAL + virtual-interface: + type: PARAVIRT + external-connection-point-ref: vnf-mgmt + - name: mgmtVM-eth1 + position: '2' + type: INTERNAL + virtual-interface: + type: PARAVIRT + internal-connection-point-ref: mgmtVM-internal + internal-connection-point: + - id: mgmtVM-internal + name: mgmtVM-internal + short-name: mgmtVM-internal + type: VPORT + cloud-init-file: cloud-config.txt + - id: dataVM + name: dataVM + image: hackfest3-mgmt + count: '1' + vm-flavor: + vcpu-count: '1' + memory-mb: '1024' + storage-gb: '10' + interface: + - name: dataVM-eth0 + position: '1' + type: INTERNAL + virtual-interface: + type: PARAVIRT + internal-connection-point-ref: dataVM-internal + - name: dataVM-xe0 + position: '2' + type: EXTERNAL + virtual-interface: + type: PARAVIRT + external-connection-point-ref: vnf-data + internal-connection-point: + - id: dataVM-internal + name: dataVM-internal + short-name: dataVM-internal + type: VPORT + vnf-configuration: + juju: + charm: simple + proxy: true + initial-config-primitive: + - seq: '1' + name: touch + parameter: + - name: filename + value: '/home/ubuntu/first-touch' + config-primitive: + - name: touch + parameter: + - name: filename + data-type: STRING + default-value: '/home/ubuntu/touched' + """ + + # @pytest.mark.serial + @pytest.mark.asyncio + async def test_charm_proxy(self, event_loop): + """Deploy and execute the initial-config-primitive of a VNF.""" + + if self.nsd and self.vnfd: + vnf_index = 0 + + for config in self.get_config(): + juju = config['juju'] + charm = juju['charm'] + + await self.deploy( + vnf_index, + charm, + config, + event_loop, + ) + + while await self.running(): + print("Waiting for test to finish...") + await asyncio.sleep(15) + logging.debug("test_charm_proxy stopped") + + return 'ok' -- 2.17.1