Commit d433b37c authored by garciadav's avatar garciadav
Browse files

Fix bug 1987

parent 11b10ead
Pipeline #5488 passed with stage
in 1 minute and 40 seconds
# Overview
This is an example of an Open Source Mano (OSM) Network Service (ns) charm. This is an **experimental** feature, expected to be released with OSM R6 in the spring of 2019.
This allows a charm to coordinate the execution of [actions] across multiple charms within a model.
# Usage
# Application Names
Each deployed application is named using a set of runtime variables, such as the network service name, the vnf or vdu name, and the vnf-member-index. Because of that, we'll need to provide those values to the NS charm through its configuration.
# Configuration
The `config.yaml` file contains a mapping of properties that will be passed from OSM to your deployed charm. Some of those properties are automatically included by the underlying `osm-ns` layer:
- juju-username
- juju-password
You'll need to pass the name of the network service that you deployed. Even though your network service may be named `nscharm` in the descriptor, the operator provides a name at instantiation time, and `osm-ns` needs this name in order to resolve the application names. You can call it anything you like, as long as it's consistent between the NS charm and the NS Descriptor.
For each VNF or VDU with a charm you wish to interact with, you need to also pass it's id from the VNF Descriptor. Again, you can call this anything you like, as long as it's consistent between the NS charm and NS Descriptor.
`config.yaml`:
```yaml
options:
nsr-name:
default:
description: The runtime name of the Network Service, i.e., what its deployed name is.
type: string
user-member-index:
default:
description: The vnf-member-index of the user VNF.
type: string
user-vdu-id:
default:
description: The id of the VDU containing the charm.
type: string
policy-member-index:
default:
description: The vnf-member-index of the policy VNF.
type: string
policy-vdu-id:
default:
description: The id of the VDU containing the charm.
type: string
```
# NS Descriptor
The NS descriptor contains a new `ns-configuration` element, similar to that of `vnf-configuration` and `vdu-configuration` available in VNF descriptors.
The key here is to pass the juju credentials from your environment, available in `~/.local/share/juju/accounts.yaml`. This enables the charm to speak directly to Juju, via the `osm-ns` layer.
Next, you must map the runtime parameters of your Network Service so that the charm is aware of your topology.
Lastly, you can then define the primitives, under `config-primitive`, to be available to the operator for day-2 operation.
```yaml
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: '50b4491a7a42d3542e317e3ae94c6c96'
# Set the runtime name of the network service
- name: nsr-name
value: 'test'
# For each vnf, map the vnf-member-index and vdu-id
- name: user-member-index
value: '1'
- name: user-vdu-id
value: 'userVM'
- name: policy-member-index
value: '2'
- name: policy-vdu-id
value: 'policyVM'
- seq: '2'
name: add-user
parameter:
- name: username
value: root
config-primitive:
- name: add-user
parameter:
- name: username
data-type: STRING
```
## Known Limitations and Issues
This functionality is EXPERIMENTAL.
# Configuration
# Contact Information
## Open Source Mano (OSM)
- [OSM website](https://osm.etsi.org/)
- [OSM bug tracker](https://osm.etsi.org/bugzilla/)
- [OSM_TECH](mailto:OSM_TECH@list.etsi.org) mailing list
- [Slack](https://join.slack.com/t/opensourcemano/shared_invite/enQtMzQ3MzYzNTQ0NDIyLWJkMzRjNDM0MjFjODYzMGQ3ODIzMzJlNTg2ZGI5OTdiZjFiNDMyMzYxMjRjNDU4N2FmNjRjNzY5NTE1MjgzOTQ)
[actions]: https://docs.jujucharms.com/2.5/en/actions
<?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:basic'
- 'layer:osm-ns'
options:
basic:
use_venv: false
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: false
"""asdfself"""
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'])
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
#!/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()
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.
__pycache__
*.pyc
.vscode
venv
.charm
build
__pycache__
\ No newline at end of file
This diff is collapsed.
# NS operator
\ No newline at end of file
#!/usr/bin/env python3
##
# Copyright 2016 Canonical Ltd.
# All rights reserved.
# Copyright 2021 Canonical Ltd.
#
# 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
......@@ -14,20 +11,29 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact: legal@canonical.com
#
# To get in touch with the maintainers, please contact:
# osm-charmers@lists.launchpad.net
##
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))
type: "charm"
bases:
- build-on:
- name: "ubuntu"
channel: "18.04"
run-on:
- name: "ubuntu"
channel: "18.04"
parts:
charm:
build-environment:
- CRYPTOGRAPHY_DONT_BUILD_RUST: 1
build-packages:
- build-essential
- libssl-dev
- libffi-dev
- python3-dev
- cargo
# Copyright 2022 David Garcia
# See LICENSE file for licensing details.
options:
juju-username:
type: string
......
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.
name: ns
description: |
This is a basic example of a Network Service charm.
summary: NS operator
series:
- focal
- bionic
- xenial
#!/usr/bin/env python3
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.
import json
import logging
import traceback
from ops.main import main
from ops.charm import CharmBase
from ops.model import ActiveStatus
logger = logging.getLogger(__name__)
class VnfPolicyCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
# Charm events
self.framework.observe(self.on.install, self._on_install)
self.framework.observe(self.on.add_user_action, self._on_add_user_action)
def _on_install(self, _):
import subprocess
subprocess.run(["apt", "install", "python3.8", "-y"])
subprocess.run(["rm", "/usr/bin/python3"])
subprocess.run(["ln", "-s", "/usr/bin/python3.8", "/usr/bin/python3"])
self.unit.status = ActiveStatus()
def _on_add_user_action(self, event):
"""Add user action."""
from ns import NetworkService
err = ""
output = ""
try:
username = event.params["username"]
bw = event.params.get("bw")
qos = event.params.get("qos")
tariff = event.params.get("tariff")
client = NetworkService(
user=self.config["juju-username"],
secret=self.config["juju-password"],
)
user_id = self._add_user(client, username, tariff)
if user_id > 0:
success = self._set_policy(client, user_id, bw, qos)
else:
logger.debug("user_id is 0; add_user failed.")
logger.debug("Output from charm: {}".format(output))
event.set_results(
{
"user-id": user_id,
"policy-set": success,
}
)
except Exception as err:
logger.error(str(err))
logger.debug(str(traceback.format_exc()))
event.fail(str(err))
def _add_user(self, client, username, tariff):
"""Add a user to the database and return the id."""
ns_config_info = json.loads(self.config["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(self, client, user_id, bw, qos):
"""Set the policy for a user."""
success = False
ns_config_info = json.loads(self.config["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.get("updated", False)
if __name__ == "__main__":
main(VnfPolicyCharm)
# 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 juju import controller
import asyncio
import logging
import os
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)
logger = logging.getLogger(__name__)
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:
logger.error("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.Controller()
logger.debug(
"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:
logger.debug("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:
logger.debug("Disconnecting from API")
self.loop.run_until_complete(self.disconnect())
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
logger.debug("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
......@@ -30,7 +30,7 @@ nsd:
- nscharm-policy-vnf
ns-configuration:
juju:
charm: ns
charm: ns_ubuntu-18.04-amd64.charm
initial-config-primitive:
- seq: 1
name: config
......
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