Commit 88f62cd0 authored by Mark Beierl's avatar Mark Beierl
Browse files

Add missing NS Charm Packages

Fetched the packages from
https://osm.etsi.org/wiki/index.php/Release_SEVEN_Integration_(DEVOPS)#Feature_6298_NS_primitives


and adds them to this repository so TS
Basic 12-Ns Primitives can execute.
Signed-off-by: Mark Beierl's avatarbeierlm <mark.beierl@canonical.com>
parent 151f590c
Pipeline #66 passed with stage
in 1 minute and 25 seconds
#!/usr/bin/env python3
# Load modules from $JUJU_CHARM_DIR/lib
import sys
sys.path.append('lib')
from charms.layer import basic # noqa
basic.bootstrap_charm_deps()
from charmhelpers.core import hookenv # noqa
hookenv.atstart(basic.init_config_states)
hookenv.atexit(basic.clear_config_states)
# This will load and run the appropriate @hook and other decorated
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
# and $JUJU_CHARM_DIR/hooks/relations.
#
# See https://jujucharms.com/docs/stable/authors-charm-building
# for more information on this pattern.
from charms.reactive import main # noqa
main()
#!/usr/bin/env python3
# Load modules from $JUJU_CHARM_DIR/lib
import sys
sys.path.append('lib')
from charms.layer import basic # noqa
basic.bootstrap_charm_deps()
from charmhelpers.core import hookenv # noqa
hookenv.atstart(basic.init_config_states)
hookenv.atexit(basic.clear_config_states)
# This will load and run the appropriate @hook and other decorated
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
# and $JUJU_CHARM_DIR/hooks/relations.
#
# See https://jujucharms.com/docs/stable/authors-charm-building
# for more information on this pattern.
from charms.reactive import main # noqa
main()
#!/usr/bin/env python3
# Load modules from $JUJU_CHARM_DIR/lib
import sys
sys.path.append('lib')
from charms.layer import basic # noqa
basic.bootstrap_charm_deps()
from charmhelpers.core import hookenv # noqa
hookenv.atstart(basic.init_config_states)
hookenv.atexit(basic.clear_config_states)
# This will load and run the appropriate @hook and other decorated
# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
# and $JUJU_CHARM_DIR/hooks/relations.
#
# See https://jujucharms.com/docs/stable/authors-charm-building
# for more information on this pattern.
from charms.reactive import main # noqa
main()
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg6517"
version="1.1"
inkscape:version="0.48+devel r12274"
sodipodi:docname="Juju_charm_icon_template.svg">
<defs
id="defs6519">
<linearGradient
inkscape:collect="always"
xlink:href="#Background"
id="linearGradient6461"
gradientUnits="userSpaceOnUse"
x1="0"
y1="970.29498"
x2="144"
y2="970.29498"
gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
<linearGradient
id="Background">
<stop
id="stop4178"
offset="0"
style="stop-color:#b8b8b8;stop-opacity:1" />
<stop
id="stop4180"
offset="1"
style="stop-color:#c9c9c9;stop-opacity:1" />
</linearGradient>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Inner Shadow"
id="filter1121">
<feFlood
flood-opacity="0.59999999999999998"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1123" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="out"
result="composite1"
id="feComposite1125" />
<feGaussianBlur
in="composite1"
stdDeviation="1"
result="blur"
id="feGaussianBlur1127" />
<feOffset
dx="0"
dy="2"
result="offset"
id="feOffset1129" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="atop"
result="composite2"
id="feComposite1131" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter950">
<feFlood
flood-opacity="0.25"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood952" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite954" />
<feGaussianBlur
in="composite1"
stdDeviation="1"
result="blur"
id="feGaussianBlur956" />
<feOffset
dx="0"
dy="1"
result="offset"
id="feOffset958" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite960" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath873">
<g
transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
id="g875"
inkscape:label="Layer 1"
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
<path
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
id="path877"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssss" />
</g>
</clipPath>
<filter
inkscape:collect="always"
id="filter891"
inkscape:label="Badge Shadow">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.71999962"
id="feGaussianBlur893" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4.0745362"
inkscape:cx="18.514671"
inkscape:cy="49.018169"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1029"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
showborder="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="false">
<inkscape:grid
type="xygrid"
id="grid821" />
<sodipodi:guide
orientation="1,0"
position="16,48"
id="guide823" />
<sodipodi:guide
orientation="0,1"
position="64,80"
id="guide825" />
<sodipodi:guide
orientation="1,0"
position="80,40"
id="guide827" />
<sodipodi:guide
orientation="0,1"
position="64,16"
id="guide829" />
</sodipodi:namedview>
<metadata
id="metadata6522">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="BACKGROUND"
inkscape:groupmode="layer"
id="layer1"
transform="translate(268,-635.29076)"
style="display:inline">
<path
style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
id="path6455"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssss" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="PLACE YOUR PICTOGRAM HERE"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="BADGE"
style="display:none"
sodipodi:insensitive="true">
<g
style="display:inline"
transform="translate(-340.00001,-581)"
id="g4394"
clip-path="none">
<g
id="g855">
<g
inkscape:groupmode="maskhelper"
id="g870"
clip-path="url(#clipPath873)"
style="opacity:0.6;filter:url(#filter891)">
<path
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
sodipodi:ry="12"
sodipodi:rx="12"
sodipodi:cy="552.36218"
sodipodi:cx="252"
id="path844"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
</g>
<g
id="g862">
<path
sodipodi:type="arc"
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path4398"
sodipodi:cx="252"
sodipodi:cy="552.36218"
sodipodi:rx="12"
sodipodi:ry="12"
d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
<path
transform="matrix(1.25,0,0,1.25,33,-100.45273)"
d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
sodipodi:ry="12"
sodipodi:rx="12"
sodipodi:cy="552.36218"
sodipodi:cx="252"
id="path4400"
style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
<path
sodipodi:type="star"
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path4459"
sodipodi:sides="5"
sodipodi:cx="666.19574"
sodipodi:cy="589.50385"
sodipodi:r1="7.2431178"
sodipodi:r2="4.3458705"
sodipodi:arg1="1.0471976"
sodipodi:arg2="1.6755161"
inkscape:flatsided="false"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z"
transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
</g>
</g>
</g>
</g>
</svg>
"includes":
- "layer:options"
- "layer:basic"
- "layer:osm-ns"
"exclude": [".travis.yml", "tests", "tox.ini", "test-requirements.txt"]
"options":
"basic":
"use_venv": !!bool "false"
"packages": ["libssl-dev"]
"python_packages": []
"include_system_packages": !!bool "false"
"osm-ns": {}
"ns": {}
"is": "ns"
import sys
from importlib import import_module
from pathlib import Path
def import_layer_libs():
"""
Ensure that all layer libraries are imported.
This makes it possible to do the following:
from charms import layer
layer.foo.do_foo_thing()
Note: This function must be called after bootstrap.
"""
for module_file in Path('lib/charms/layer').glob('*'):
module_name = module_file.stem
if module_name in ('__init__', 'basic', 'execd') or not (
module_file.suffix == '.py' or module_file.is_dir()
):
continue
import_module('charms.layer.{}'.format(module_name))
# Terrible hack to support the old terrible interface.
# Try to get people to call layer.options.get() instead so
# that we can remove this garbage.
# Cribbed from https://stackoverfLow.com/a/48100440/4941864
class OptionsBackwardsCompatibilityHack(sys.modules[__name__].__class__):
def __call__(self, section=None, layer_file=None):
if layer_file is None:
return self.get(section=section)
else:
return self.get(section=section,
layer_file=Path(layer_file))
def patch_options_interface():
from charms.layer import options
if sys.version_info.minor >= 5:
options.__class__ = OptionsBackwardsCompatibilityHack
else:
# Py 3.4 doesn't support changing the __class__, so we have to do it
# another way. The last line is needed because we already have a
# reference that doesn't get updated with sys.modules.
name = options.__name__
hack = OptionsBackwardsCompatibilityHack(name)
hack.get = options.get
sys.modules[name] = hack
sys.modules[__name__].options = hack
try:
patch_options_interface()
except ImportError:
# This may fail if pyyaml hasn't been installed yet. But in that
# case, the bootstrap logic will try it again once it has.
pass
import os
import sys
import shutil
from glob import glob
from subprocess import check_call, check_output, CalledProcessError
from time import sleep
from charms import layer
from charms.layer.execd import execd_preinstall
def lsb_release():
"""Return /etc/lsb-release in a dict"""
d = {}
with open('/etc/lsb-release', 'r') as lsb:
for l in lsb:
k, v = l.split('=')
d[k.strip()] = v.strip()
return d
def bootstrap_charm_deps():
"""
Set up the base charm dependencies so that the reactive system can run.
"""
# execd must happen first, before any attempt to install packages or
# access the network, because sites use this hook to do bespoke
# configuration and install secrets so the rest of this bootstrap
# and the charm itself can actually succeed. This call does nothing
# unless the operator has created and populated $JUJU_CHARM_DIR/exec.d.
execd_preinstall()
# ensure that $JUJU_CHARM_DIR/bin is on the path, for helper scripts
charm_dir = os.environ['JUJU_CHARM_DIR']
os.environ['PATH'] += ':%s' % os.path.join(charm_dir, 'bin')
venv = os.path.abspath('../.venv')
vbin = os.path.join(venv, 'bin')
vpip = os.path.join(vbin, 'pip')
vpy = os.path.join(vbin, 'python')
hook_name = os.path.basename(sys.argv[0])
is_bootstrapped = os.path.exists('wheelhouse/.bootstrapped')
is_charm_upgrade = hook_name == 'upgrade-charm'
is_series_upgrade = hook_name == 'post-series-upgrade'
post_upgrade = os.path.exists('wheelhouse/.upgrade')
is_upgrade = not post_upgrade and (is_charm_upgrade or is_series_upgrade)
if is_bootstrapped and not is_upgrade:
# older subordinates might have downgraded charm-env, so we should
# restore it if necessary
install_or_update_charm_env()
activate_venv()
# the .upgrade file prevents us from getting stuck in a loop
# when re-execing to activate the venv; at this point, we've
# activated the venv, so it's safe to clear it
if post_upgrade:
os.unlink('wheelhouse/.upgrade')
return
if is_series_upgrade and os.path.exists(venv):
# series upgrade should do a full clear of the venv, rather than just
# updating it, to bring in updates to Python itself
shutil.rmtree(venv)
if is_upgrade:
if os.path.exists('wheelhouse/.bootstrapped'):
os.unlink('wheelhouse/.bootstrapped')
open('wheelhouse/.upgrade', 'w').close()
# bootstrap wheelhouse
if os.path.exists('wheelhouse'):
with open('/root/.pydistutils.cfg', 'w') as fp:
# make sure that easy_install also only uses the wheelhouse
# (see https://github.com/pypa/pip/issues/410)
fp.writelines([
"[easy_install]\n",
"allow_hosts = ''\n",
"find_links = file://{}/wheelhouse/\n".format(charm_dir),
])
apt_install([
'python3-pip',
'python3-setuptools',
'python3-yaml',
'python3-dev',
'python3-wheel',
'build-essential',
])
from charms.layer import options
cfg = options.get('basic')
# include packages defined in layer.yaml
apt_install(cfg.get('packages', []))
# if we're using a venv, set it up
if cfg.get('use_venv'):
if not os.path.exists(venv):
series = lsb_release()['DISTRIB_CODENAME']
if series in ('precise', 'trusty'):
apt_install(['python-virtualenv'])
else:
apt_install(['virtualenv'])
cmd = ['virtualenv', '-ppython3', '--never-download', venv]
if cfg.get('include_system_packages'):
cmd.append('--system-site-packages')
check_call(cmd)
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
pip = vpip
else:
pip = 'pip3'
# save a copy of system pip to prevent `pip3 install -U pip`
# from changing it
if os.path.exists('/usr/bin/pip'):
shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
# need newer pip, to fix spurious Double Requirement error:
# https://github.com/pypa/pip/issues/56
check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
'pip'])
# per https://github.com/juju-solutions/layer-basic/issues/110
# this replaces the setuptools that was copied over from the system on
# venv create with latest setuptools and adds setuptools_scm
check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
'setuptools', 'setuptools-scm'])
# install the rest of the wheelhouse deps
check_call([pip, 'install', '-U', '--ignore-installed', '--no-index',
'-f', 'wheelhouse'] + glob('wheelhouse/*'))
# re-enable installation from pypi
os.remove('/root/.pydistutils.cfg')
# install python packages from layer options
if cfg.get('python_packages'):
check_call([pip, 'install', '-U'] + cfg.get('python_packages'))
if not cfg.get('use_venv'):
# restore system pip to prevent `pip3 install -U pip`
# from changing it
if os.path.exists('/usr/bin/pip.save'):
shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
os.remove('/usr/bin/pip.save')
# setup wrappers to ensure envs are used for scripts
install_or_update_charm_env()
for wrapper in ('charms.reactive', 'charms.reactive.sh',
'chlp', 'layer_option'):
src = os.path.join('/usr/local/sbin', 'charm-env')
dst = os.path.join('/usr/local/sbin', wrapper)
if not os.path.exists(dst):
os.symlink(src, dst)
if cfg.get('use_venv'):
shutil.copy2('bin/layer_option', vbin)
else:
shutil.copy2('bin/layer_option', '/usr/local/bin/')
# re-link the charm copy to the wrapper in case charms
# call bin/layer_option directly (as was the old pattern)
os.remove('bin/layer_option')
os.symlink('/usr/local/sbin/layer_option', 'bin/layer_option')
# flag us as having already bootstrapped so we don't do it again
open('wheelhouse/.bootstrapped', 'w').close()
# Ensure that the newly bootstrapped libs are available.
# Note: this only seems to be an issue with namespace packages.
# Non-namespace-package libs (e.g., charmhelpers) are available
# without having to reload the interpreter. :/
reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
def install_or_update_charm_env():
# On Trusty python3-pkg-resources is not installed
try:
from pkg_resources import parse_version
except ImportError:
apt_install(['python3-pkg-resources'])
from pkg_resources import parse_version
try:
installed_version = parse_version(
check_output(['/usr/local/sbin/charm-env',
'--version']).decode('utf8'))
except (CalledProcessError, FileNotFoundError):
installed_version = parse_version('0.0.0')
try:
bundled_version = parse_version(
check_output(['bin/charm-env',
'--version']).decode('utf8'))
except (CalledProcessError, FileNotFoundError):
bundled_version = parse_version('0.0.0')
if installed_version < bundled_version:
shutil.copy2('bin/charm-env', '/usr/local/sbin/')
def activate_venv():
"""
Activate the venv if enabled in ``layer.yaml``.
This is handled automatically for normal hooks, but actions might
need to invoke this manually, using something like:
# Load modules from $JUJU_CHARM_DIR/lib
import sys
sys.path.append('lib')
from charms.layer.basic import activate_venv
activate_venv()
This will ensure that modules installed in the charm's
virtual environment are available to the action.
"""
from charms.layer import options
venv = os.path.abspath('../.venv')
vbin = os.path.join(venv, 'bin')
vpy = os.path.join(vbin, 'python')
use_venv = options.get('basic', 'use_venv')
if use_venv and '.venv' not in sys.executable:
# activate the venv
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
reload_interpreter(vpy)
layer.patch_options_interface()
layer.import_layer_libs()
def reload_interpreter(python):
"""
Reload the python interpreter to ensure that all deps are available.
Newly installed modules in namespace packages sometimes seemt to
not be picked up by Python 3.
"""
os.execve(python, [python] + list(sys.argv), os.environ)
def apt_install(packages):
"""
Install apt packages.
This ensures a consistent set of options that are often missed but
should really be set.
"""
if isinstance(packages, (str, bytes)):
packages = [packages]
env = os.environ.copy()
if 'DEBIAN_FRONTEND' not in env:
env['DEBIAN_FRONTEND'] = 'noninteractive'
cmd = ['apt-get',
'--option=Dpkg::Options::=--force-confold',
'--assume-yes',
'install']
for attempt in range(3):
try:
check_call(cmd + packages, env=env)
except CalledProcessError:
if attempt == 2: # third attempt
raise
try:
# sometimes apt-get update needs to be run
check_call(['apt-get', 'update'])
except CalledProcessError:
# sometimes it's a dpkg lock issue
pass
sleep(5)
else:
break
def init_config_states():
import yaml
from charmhelpers.core import hookenv
from charms.reactive import set_state
from charms.reactive import toggle_state
config = hookenv.config()
config_defaults = {}
config_defs = {}
config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
if os.path.exists(config_yaml):
with open(config_yaml) as fp:
config_defs = yaml.safe_load(fp).get('options', {})
config_defaults = {key: value.get('default')
for key, value in config_defs.items()}
for opt in config_defs.keys():
if config.changed(opt):
set_state('config.changed')
set_state('config.changed.{}'.format(opt))
toggle_state('config.set.{}'.format(opt), config.get(opt))
toggle_state('config.default.{}'.format(opt),
config.get(opt) == config_defaults[opt])
def clear_config_states():
from charmhelpers.core import hookenv, unitdata
from charms.reactive import remove_state
config = hookenv.config()
remove_state('config.changed')
for opt in config.keys():
remove_state('config.changed.{}'.format(opt))
remove_state('config.set.{}'.format(opt))
remove_state('config.default.{}'.format(opt))
unitdata.kv().flush()
# Copyright 2014-2016 Canonical Limited.
#
# This file is part of layer-basic, the reactive base layer for Juju.
#
# charm-helpers is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3 as
# published by the Free Software Foundation.
#
# charm-helpers is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
# This module may only import from the Python standard library.
import os
import sys
import subprocess
import time
'''
execd/preinstall
Read the layer-basic docs for more info on how to use this feature.
https://charmsreactive.readthedocs.io/en/latest/layer-basic.html#exec-d-support
'''
def default_execd_dir():
return os.path.join(os.environ['JUJU_CHARM_DIR'], 'exec.d')
def execd_module_paths(execd_dir=None):
"""Generate a list of full paths to modules within execd_dir."""
if not execd_dir:
execd_dir = default_execd_dir()
if not os.path.exists(execd_dir):
return
for subpath in os.listdir(execd_dir):
module = os.path.join(execd_dir, subpath)
if os.path.isdir(module):
yield module
def execd_submodule_paths(command, execd_dir=None):
"""Generate a list of full paths to the specified command within exec_dir.
"""
for module_path in execd_module_paths(execd_dir):
path = os.path.join(module_path, command)
if os.access(path, os.X_OK) and os.path.isfile(path):
yield path
def execd_sentinel_path(submodule_path):
module_path = os.path.dirname(submodule_path)
execd_path = os.path.dirname(module_path)
module_name = os.path.basename(module_path)
submodule_name = os.path.basename(submodule_path)
return os.path.join(execd_path,
'.{}_{}.done'.format(module_name, submodule_name))
def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
"""Run command for each module within execd_dir which defines it."""
if stderr is None:
stderr = sys.stdout
for submodule_path in execd_submodule_paths(command, execd_dir):
# Only run each execd once. We cannot simply run them in the
# install hook, as potentially storage hooks are run before that.
# We cannot rely on them being idempotent.
sentinel = execd_sentinel_path(submodule_path)
if os.path.exists(sentinel):
continue
try:
subprocess.check_call([submodule_path], stderr=stderr,
universal_newlines=True)
with open(sentinel, 'w') as f:
f.write('{} ran successfully {}\n'.format(submodule_path,
time.ctime()))
f.write('Removing this file will cause it to be run again\n')
except subprocess.CalledProcessError as e:
# Logs get the details. We can't use juju-log, as the
# output may be substantial and exceed command line
# length limits.
print("ERROR ({}) running {}".format(e.returncode, e.cmd),
file=stderr)
print("STDOUT<<EOM", file=stderr)
print(e.output, file=stderr)
print("EOM", file=stderr)
# Unit workload status gets a shorter fail message.
short_path = os.path.relpath(submodule_path)
block_msg = "Error ({}) running {}".format(e.returncode,
short_path)
try:
subprocess.check_call(['status-set', 'blocked', block_msg],
universal_newlines=True)
if stop_on_error:
sys.exit(0) # Leave unit in blocked state.
except Exception:
pass # We care about the exec.d/* failure, not status-set.
if stop_on_error:
sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
def execd_preinstall(execd_dir=None):
"""Run charm-pre-install for each module within execd_dir."""
execd_run('charm-pre-install', execd_dir=execd_dir)
import os
from pathlib import Path
import yaml
_CHARM_PATH = Path(os.environ.get('JUJU_CHARM_DIR', '.'))
_DEFAULT_FILE = _CHARM_PATH / 'layer.yaml'
_CACHE = {}
def get(section=None, option=None, layer_file=_DEFAULT_FILE):
if option and not section:
raise ValueError('Cannot specify option without section')
layer_file = (_CHARM_PATH / layer_file).resolve()
if layer_file not in _CACHE:
with layer_file.open() as fp:
_CACHE[layer_file] = yaml.safe_load(fp.read())
data = _CACHE[layer_file].get('options', {})
if section:
data = data.get(section, {})
if option:
data = data.get(option)
return data
# A prototype of a library to aid in the development and operation of
# OSM Network Service charms
# This class handles the heavy lifting associated with asyncio.
from charmhelpers.core.hookenv import (
log,
)
try:
import juju
except ImportError:
import subprocess
log('Installing juju')
subprocess.run(["pip3", "-qqq", "install", "juju"])
import asyncio
import logging
from juju.controller import Controller
import os
import os.path
import re
import time
import yaml
# Quiet the debug logging
logging.getLogger('websockets.protocol').setLevel(logging.INFO)
logging.getLogger('juju.client.connection').setLevel(logging.WARN)
logging.getLogger('juju.model').setLevel(logging.WARN)
logging.getLogger('juju.machine').setLevel(logging.WARN)
class NetworkService:
"""A lightweight interface to the Juju controller.
This NetworkService client is specifically designed to allow a higher-level
"NS" charm to interoperate with "VNF" charms, allowing for the execution of
Primitives across other charms within the same model.
"""
endpoint = None
user = 'admin'
secret = None
port = 17070
loop = None
client = None
model = None
cacert = None
def __init__(self, user, secret, endpoint=None):
self.user = user
self.secret = secret
if endpoint is None:
addresses = os.environ['JUJU_API_ADDRESSES']
for address in addresses.split(' '):
self.endpoint = address
else:
self.endpoint = endpoint
# Stash the name of the model
self.model = os.environ['JUJU_MODEL_NAME']
# Load the ca-cert from agent.conf
AGENT_PATH = os.path.dirname(os.environ['JUJU_CHARM_DIR'])
with open("{}/agent.conf".format(AGENT_PATH), "r") as f:
try:
y = yaml.safe_load(f)
self.cacert = y['cacert']
except yaml.YAMLError as exc:
log("Unable to find Juju ca-cert.")
raise exc
# Create our event loop
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
async def connect(self):
"""Connect to the Juju controller."""
controller = Controller()
log(
"Connecting to controller... ws://{}:{} as {}/{}".format(
self.endpoint,
self.port,
self.user,
self.secret[-4:].rjust(len(self.secret), "*"),
)
)
await controller.connect(
endpoint=self.endpoint,
username=self.user,
password=self.secret,
cacert=self.cacert,
)
return controller
def __del__(self):
self.logout()
async def disconnect(self):
"""Disconnect from the Juju controller."""
if self.client:
log("Disconnecting Juju controller")
await self.client.disconnect()
def login(self):
"""Login to the Juju controller."""
if not self.client:
# Connect to the Juju API server
self.client = self.loop.run_until_complete(self.connect())
return self.client
def logout(self):
"""Logout of the Juju controller."""
if self.loop:
log("Disconnecting from API")
self.loop.run_until_complete(self.disconnect())
def FormatApplicationName(self, *args):
"""
Generate a Juju-compatible Application name
:param args tuple: Positional arguments to be used to construct the
application name.
Limitations::
- Only accepts characters a-z and non-consequitive dashes (-)
- Application name should not exceed 50 characters
Examples::
FormatApplicationName("ping_pong_ns", "ping_vnf", "a")
"""
appname = ""
for c in "-".join(list(args)):
if c.isdigit():
c = chr(97 + int(c))
elif not c.isalpha():
c = "-"
appname += c
return re.sub('-+', '-', appname.lower())
def GetApplicationName(self, vnf_member_index, vdu_id=None):
"""Get the runtime application name of a VNF/VDU.
This will generate an application name matching the name of the deployed charm,
given the right parameters.
:param vnf_member_index str: The vnf-member-index of the VNF
:param vdu_id str: The vdu:id as specified in the descriptor
"""
# Get the NSR name from the Juju Unit name
# i.e., JUJU_UNIT_NAME=testc-ac/0
# or JUJU_UNIT_NAME=asdf-asdf-ac/0 (a NS name with a hyphen)
app = os.environ['JUJU_UNIT_NAME']
nsr_name = app[:app.rindex('-')]
vdu_id = (vdu_id if vdu_id else "") + "-"
application_name = self.FormatApplicationName(nsr_name, vnf_member_index, vdu_id)
# This matches the logic used by the LCM
application_name = application_name[0:48]
vca_index = int(vnf_member_index) - 1
application_name += chr(97 + vca_index // 26) + chr(97 + vca_index % 26)
return application_name
def ExecutePrimitiveGetOutput(self, application, primitive, params={}, timeout=600):
"""Execute a single primitive and return it's output.
This is a blocking method that will execute a single primitive and wait
for its completion before return it's output.
:param application str: The application name provided by `GetApplicationName`.
:param primitive str: The name of the primitive to execute.
:param params list: A list of parameters.
:param timeout int: A timeout, in seconds, to wait for the primitive to finish. Defaults to 600 seconds.
"""
uuid = self.ExecutePrimitive(application, primitive, params)
status = None
output = None
starttime = time.time()
while(time.time() < starttime + timeout):
status = self.GetPrimitiveStatus(uuid)
if status in ['completed', 'failed']:
break
time.sleep(10)
# When the primitive is done, get the output
if status in ['completed', 'failed']:
output = self.GetPrimitiveOutput(uuid)
return output
def ExecutePrimitive(self, application, primitive, params={}):
"""Execute a primitive.
This is a non-blocking method to execute a primitive. It will return
the UUID of the queued primitive execution, which you can use
for subsequent calls to `GetPrimitiveStatus` and `GetPrimitiveOutput`.
:param application string: The name of the application
:param primitive string: The name of the Primitive.
:param params list: A list of parameters.
:returns uuid string: The UUID of the executed Primitive
"""
uuid = None
if not self.client:
self.login()
model = self.loop.run_until_complete(
self.client.get_model(self.model)
)
# Get the application
if application in model.applications:
app = model.applications[application]
# Execute the primitive
unit = app.units[0]
if unit:
action = self.loop.run_until_complete(
unit.run_action(primitive, **params)
)
uuid = action.id
log("Executing action: {}".format(uuid))
self.loop.run_until_complete(
model.disconnect()
)
else:
# Invalid mapping: application not found. Raise exception
raise Exception("Application not found: {}".format(application))
return uuid
def GetPrimitiveStatus(self, uuid):
"""Get the status of a Primitive execution.
This will return one of the following strings:
- pending
- running
- completed
- failed
:param uuid string: The UUID of the executed Primitive.
:returns: The status of the executed Primitive
"""
status = None
if not self.client:
self.login()
model = self.loop.run_until_complete(
self.client.get_model(self.model)
)
status = self.loop.run_until_complete(
model.get_action_status(uuid)
)
self.loop.run_until_complete(
model.disconnect()
)
return status[uuid]
def GetPrimitiveOutput(self, uuid):
"""Get the output of a completed Primitive execution.
:param uuid string: The UUID of the executed Primitive.
:returns: The output of the execution, or None if it's still running.
"""
result = None
if not self.client:
self.login()
model = self.loop.run_until_complete(
self.client.get_model(self.model)
)
result = self.loop.run_until_complete(
model.get_action_output(uuid)
)
self.loop.run_until_complete(
model.disconnect()
)
return result
"name": "ns"
"summary": "<Fill in summary here>"
"maintainer": "Adam Israel <Adam.Israel@maidalchini>"
"description": |
<Multi-line description here>
"tags":
# Replace "misc" with one or more whitelisted tags from this list:
# https://jujucharms.com/docs/stable/authors-charm-metadata
- "misc"
"series":
- "xenial"
- "bionic"
"subordinate": !!bool "false"
from charmhelpers.core.hookenv import (
action_get,
action_fail,
action_set,
status_set,
log,
config,
)
from charms.reactive import (
clear_flag,
set_flag,
when,
when_not,
)
import charms.osm.ns
import json
import traceback
@when_not('ns.installed')
def install_ns():
set_flag('ns.installed')
status_set('active', 'Ready!')
@when('actions.add-user')
def action_add_user():
"""Add a user to the database."""
err = ''
output = ''
try:
username = action_get('username')
bw = action_get('bw')
qos = action_get('qos')
tariff = action_get('tariff')
# Get the configuration, which should contain the juju username and
# password. The endpoint and model will be discovered automatically
cfg = config()
client = charms.osm.ns.NetworkService(
user=cfg['juju-username'],
secret=cfg['juju-password'],
)
user_id = add_user(client, username, tariff)
if user_id > 0:
success = set_policy(client, user_id, bw, qos)
else:
log("user_id is 0; add_user failed.")
log("Output from charm: {}".format(output))
except Exception as err:
log(str(err))
log(str(traceback.format_exc()))
action_fail(str(err))
else:
action_set({
'user-id': user_id,
'policy-set': success,
})
finally:
clear_flag('actions.add-user')
def add_user(client, username, tariff):
"""Add a user to the database and return the id."""
cfg = config()
ns_config_info = json.loads(cfg['ns_config_info'])
log(ns_config_info)
application = ns_config_info['osm-config-mapping']['{}.{}.{}'.format(
'1', # member-vnf-index
'userVM', # vdu_id
'0', # vdu_count_index
)]
output = client.ExecutePrimitiveGetOutput(
# The name of the application for adding a user
application,
# The name of the action to call
"add-user",
# The parameter(s) required by the above charm and action
params={
'username': username,
'tariff': tariff,
},
# How long to wait (in seconds) for the action to finish
timeout=500
)
# Get the output from the `add-user` function
user_id = int(output['user-id'])
return user_id
def set_policy(client, user_id, bw, qos):
"""Set the policy for a user."""
success = False
cfg = config()
ns_config_info = json.loads(cfg['ns_config_info'])
application = ns_config_info['osm-config-mapping']['{}.{}.{}'.format(
'2', # member-vnf-index
'policyVM', # vdu_id
'0', # vdu_count_index
)]
success = client.ExecutePrimitiveGetOutput(
# The name of the application for policy management
application,
# The name of the action to call
"set-policy",
# The parameter(s) required by the above charm and action
params={
'user_id': user_id,
'bw': bw,
'qos': qos,
},
# How long to wait (in seconds) for the action to finish
timeout=500
)
# Get the output from the `add-user` function
return success
from charms.reactive import when, when_not, set_flag
@when_not('osm-stack.installed')
def install_osm_stack():
set_flag('osm-stack.installed')
#!/bin/bash
sudo add-apt-repository ppa:juju/stable -y
sudo apt-get update
sudo apt-get install amulet python-requests -y
#!/usr/bin/python3
import amulet
import requests
import unittest
class TestCharm(unittest.TestCase):
def setUp(self):
self.d = amulet.Deployment()
self.d.add('ns')
self.d.expose('ns')
self.d.setup(timeout=900)
self.d.sentry.wait()
self.unit = self.d.sentry['ns'][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()
aa34ff8-dirty
\ No newline at end of file
nsd:nsd-catalog:
nsd:
- id: nscharm-ns
name: nscharm-ns
short-name: nscharm-ns
description: NS with 2 VNFs
version: '1.0'
logo: osm.png
constituent-vnfd:
- vnfd-id-ref: nscharm-user-vnf
member-vnf-index: '1'
- vnfd-id-ref: nscharm-policy-vnf
member-vnf-index: '2'
vld:
- id: mgmtnet
name: mgmtnet
short-name: mgmtnet
type: ELAN
mgmt-network: 'true'
vim-network-name: mgmt
vnfd-connection-point-ref:
- vnfd-id-ref: nscharm-user-vnf
member-vnf-index-ref: '1'
vnfd-connection-point-ref: vnf-mgmt
- vnfd-id-ref: nscharm-policy-vnf
member-vnf-index-ref: '2'
vnfd-connection-point-ref: vnf-mgmt
ns-configuration:
juju:
charm: ns
initial-config-primitive:
- seq: '1'
name: config
parameter:
# Configure Juju credentials
- name: juju-username
value: 'admin'
- name: juju-password
value: 'd55ce8ab4efa59e7f1b865bce53f55ed'
- seq: '2'
name: add-user
parameter:
- name: username
value: root
config-primitive:
- name: add-user
parameter:
- name: username
data-type: STRING
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment