Talk: Creating your own VNF charm (Release ONE): Difference between revisions

From OSM Public Wiki
Jump to: navigation, search
 
(One intermediate revision by the same user not shown)
Line 215: Line 215:
The above code loads the Reactive charm framework and sets the 'actions.benchmark' state, which will be executed in reactive/aaa.py:
The above code loads the Reactive charm framework and sets the 'actions.benchmark' state, which will be executed in reactive/aaa.py:


<nowiki>
<nowiki>
@when('actions.benchmark')
@when('actions.benchmark')
def benchmark():
def benchmark():
Line 227: Line 227:
         action_set({'outout': result})
         action_set({'outout': result})
     finally:
     finally:
         remove_flag('actions.set-server')
         remove_flag('actions.benchmark')
</nowiki>
</nowiki>



Latest revision as of 19:30, 21 March 2017

Introduction to Juju and charms in OSM

A charm is a collection of scripts and metadata that encapsulate the distilled DevOps knowledge of experts in a particular product. Charms make it easy to reliably and repeatedly deploy applications, then scale them as required with minimal effort.

Driven by Juju, these charms manage the complete lifecycle of the application, including installation, configuration, clustering, and scaling.

In the context of OSM, Juju, through the use of charms, is responsible for VNF configuration and life-cycle management, excluding resource operations (instantiation, termination). This is called "manual provider" mode in Juju.

Charms can reside inside the VNF ("native" charms) or outside the VNF ("proxy" charms). Configurations are mapped to Juju Actions which manage configuration within the VNF. In the case of the proxy charm, the configuration might be done via SSH, via RESTful API, etc.

The diagram below illustrates the OSM workflow in the case of a proxy charm:

+---------------------+    +---------------------+
|                     +---->                     |
|  Service            |    |  Resource           |
|  Orchestrator (SO)  <----+  Orchestrator (RO)  |
|                     |    |                     |
+---------+----^------+    +----------+----------+
          |    |                      |
          |    |                      |
          |    |                      |
        +-v----+--+             +-----v-----+
        |         +------------->           |
        |  Proxy  |             |  Virtual  |
        |  Charm  |             |  Machine  |
        |         <-------------+           |
        +---------+             +-----------+

The SO directs the RO to create a virtual machine using the selected VNF image. When that has successfully completed, the SO will instantiate a LXD container, managed by Juju, with the proxy charm. The proxy charm will then communicate with the VNF virtual machine to do Day 1 configuration. Once the VNF is configured and running, NS primitives in the SO could trigger other configuration actions in the VNFs as part of the service itself.

Setup

If you are running Ubuntu, you can install the latest Juju via its stable PPA:

sudo add-apt-repository ppa:juju/stable
sudo apt update
sudo apt install juju charm

Then, you will have to clone the juju-charms repo and set some env variables to your system

cd
git clone https://osm.etsi.org/gerrit/osm/juju-charms
export JUJU_REPOSITORY=~/juju-charms
export LAYER_PATH=$JUJU_REPOSITORY/layers
export LAYER_PATH=$JUJU_REPOSITORY/interfaces

You can add those env variables to your .bashrc file

Creating a charm

This section describes the creation of a charm for a vendor's AAA VNF to automate configuration via a vendor's vEMS.

An additional charm would be required for the vEMS to use the interface provided by the AAA VNF.

Initial skeleton

Charms must be created in the layers folder ($LAYER_PATH). The command "charm create" will create the skeleton.

cd $LAYER_PATH
charm create vendor-aaa
cd vendor-aaa

metadata.yaml

Modify metadata.yaml. This file describes what your charm is and sets certain properties used by Juju. Typically you should specify at least:

  • name: the name of the charm
  • maintainer: contact info for the charm
  • provides: what interfaces are provided by the charm
  • requires: what interfaces are used by the charm
  • peers: what peer relations exist
  • series: which base image to use for the container running the charm

In our case, our Vendor AAA will use xenial as the container image and will only use an interface provided by the vendor EMS:

name: vendor-aaa
summary: Charm for Vendor's AAA VNF
maintainer: Automatically generated
description: |
  Charm for Vendor's AAA VNF
tags:
  # Replace "misc" with one or more whitelisted tags from this list:
  # https://jujucharms.com/docs/stable/authors-charm-metadata
  - misc
subordinate: false
series:
  - xenial
requires:
  aaaconfig:
    interface: aaaconfig


layer.yaml

Next, modify layer.yaml to specify which other layers should be "imported" as part of the charm. Layers are individual components that, when combined, result in a finished product. The diagram below describes an example of the layers contained in this charm.

+------------------+
|                  |
|      Layers      |
|                  |
|  +------------+  |
|  |            |  |
|  |   basic    |  |
|  |            |  |
|  +------------+  |
|                  |            +-----------------+
|  +------------+  |            |                 |
|  |            |  |            |    vendor-aaa   |
|  |  vnfproxy  |  +------------>                 |
|  |            |  |            |      charm      |
|  +------------+  |            |                 |
|                  |            +-----------------+
|  +------------+  |
|  |            |  |
|  |  metrics   |  |
|  |            |  |
|  +------------+  |
|                  |
+------------------+

Typical layers that are included as part of the charm are the basic and the vnfproxy layer, but others might be added. In this example, the metrics layer is also added:

includes:
    - layer:basic
    - layer:vnfproxy
    - layer:metrics

It must be noted that these layers can include subsequently other layers. When building the charms, all layers included in layer.yaml and its dependent layers will be combined in the final charm.

Some of the most used layers in the context of OSM are:

  • layer:basic -> imports the reactive framework
  • layer:vnfproxy -> imports the required functions to run actions in the VNF via SSH. This layer has been designed to aid in the development of proxy charms.
  • layer:metrics -> imports the required functions to get metrics from the VNF
  • layer:restapi -> imports the required functions to run actions in the VNF via REST API
  • layer:netconf -> imports the required functions to run actions in the VNF via Netconf primitives

More layers can be found here: TO BE COMPLETED

config.yaml

Next, modify config.yaml to specify which actions can be called and drive other events.

TO BE COMPLETED


metrics.yaml

Next, if the VNF wants to expose some metrics, a new file metrics.yaml has to be created to specify which metrics should be collected.

metrics:
  users:
    type: gauge
    description: Number of users
    command: scripts/count_users.py
  tokens:
    type: gauge
    description: Number of active tokens
    command: scripts/count_tokens.py

actions.yaml

The actions.yaml file declares tasks that can be run on-demand against a VNF.

add-user:
    description: "Add a user"
    params:
        username:
            type: string
            description: ""
            default: "demo"
        password:
            type: string
            description: ""
            default: "demo"
backup:
    description: "Create a backup of the VNF"
restore:
    description: "Restore the VNF from a backup"
benchmark:
    description: "Benchmark the VNF"
    params:
        time:
            type: integer
            description: "Number of seconds to run the benchmark"
            default: 60

Implement actions

There are two steps to implementing an action. The first is creating the entrypoint that will execute the logic behind the action. This entrypoint, shown below, should be copied into a file in the actions/ directory and made executable. The name of the file must match the name of the action.

TO BE COMPLETED

cat <<'EOF' >> actions/benchmark
#!/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, 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_state('actions.{}'.format(action_name()))

try:
    main()
except Exception as e:
    action_fail(repr(e))
EOF

chmod +x actions/run


The above code loads the Reactive charm framework and sets the 'actions.benchmark' state, which will be executed in reactive/aaa.py:

@when('actions.benchmark')
def benchmark():
    err = ''
    try:
        cmd = "" # Put the code you want executed here.
        result, err = charms.sshproxy._run(cmd)
    except:
        action_fail('command failed:' + err)
    else:
        action_set({'outout': result})
    finally:
        remove_flag('actions.benchmark')

Building the charm

Be sure that you are in the charm folder (e.g.: $LAYER_PATH/vendor-aaa) and you have Internet connectivity before building the charm.

charm build

Built charms are stored in $JUJU_REPOSITORY/builds

Updating VNF Descriptor

TO BE COMPLETED


Debugging a charm

Once you have created a VNF package with the charm and a NS package using the previous VNF, you need to instantiate it in OSM following instructions here.

Example. Pingpong VNF

This section describes the creation of a charm for a simple VNF "pingpong" that can act as ping client or ping server (pong).

The diagram below describes what our example pingpong charm looks like, followed by a walkthrough of how it is built. This example is based on the pingpong charm available in the juju-charms repository.

+------------------+
|                  |
|      Layers      |
|                  |
|  +------------+  |
|  |            |  |
|  |   basic    |  |
|  |            |  |
|  +------------+  |
|                  |            +-----------------+
|  +------------+  |            |                 |
|  |            |  |            |     pingpong    |
|  |  vnfproxy  |  +------------>                 |
|  |            |  |            |      charm      |
|  +------------+  |            |                 |
|                  |            +-----------------+
|  +------------+  |
|  |            |  |
|  |  pingpong  |  |
|  |            |  |
|  +------------+  |
|                  |
+------------------+

The charm will use existing layers "vnfproxy" and "basic", and will use a new layer "pingpong".

Initial skeleton

charm create pingpong
cd pingpong

This will create a charm layer ready for customization:

.
├── config.yaml
├── icon.svg
├── layer.yaml
├── metadata.yaml
├── reactive
│   └── pingpong.py
├── README.ex
└── tests
    ├── 00-setup
    └── 10-deploy

metadata.yaml

Next, modify the metadata.yaml file.

name: pingpong
summary: A service to test latency between machines.
maintainer: Adam Israel <adam.israel@canonical.com>
description: |
  The pingpong charm manages the pingpong VNF deployed by OSM.
tags:
  - nfv
subordinate: false
series:
    - trusty
    - xenial

layer.yaml

Next, modify layer.yaml to the following:

includes:
    - layer:basic
    - layer:vnfproxy

This means that your charm will include the basic layer, required for all charms, and the vnfproxy layer.


config.yaml

Next, modify config.yaml to specify which actions can be called and drive other events.

TO BE COMPLETED


metrics.yaml

Next, if the VNF wants to expose some metrics, a new file metrics.yaml has to be created to specify which parameters can be configured

TO BE COMPLETED


actions.yaml

In actions.yaml, we define the actions we wish to support:

set-server:
    description: "Set the target IP address and port"
    params:
        server-ip:
            description: "IP on which the target service is listening."
            type: string
            default: ""
        server-port:
            description: "Port on which the target service is listening."
            type: integer
            default: 5555
    required:
        - server-ip
set-rate:
    description: "Set the rate of packet generation."
    params:
        rate:
            description: "Packet rate."
            type: integer
            default: 5
get-stats:
    description: "Get the stats."
get-state:
    description: "Get the admin state of the target service."
get-rate:
    description: "Get the rate set on the target service."
get-server:
    description: "Get the target server and IP set"

Implement actions

We need to create the actions folder.

mkdir actions/

Then, for each action, we need to create a script to invoke the reactive framework. This is a boilerplate script that will be used for every action. The first step is to create the first action script and make it executable.


cat <<'EOF' >> actions/set-server
#!/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, 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_state('actions.{}'.format(action_name()))

try:
    main()
except Exception as e:
    action_fail(repr(e))
EOF

chmod +x actions/run

Next, copy this script for the remaining actions:

cp actions/set-server actions/set-rate
cp actions/set-server actions/get-stats
cp actions/set-server actions/set-state
cp actions/set-server actions/get-rate
cp actions/set-server actions/get-server

The last step is to map the action to the command(s) to be run. To do this, open up reactive/pingpong.py and add this code.

@when('actions.set-server')
def set_server():
    err = ''
    try:
        cmd = ""
        result, err = charms.sshproxy._run(cmd)
    except:
        action_fail('command failed:' + err)
    else:
        action_set({'outout': result})
    finally:
        remove_flag('actions.set-server')

The reactive framework, coupled with the script in the actions/ directory, maps the SO's invocation of the action to the block of code with the matching @when decorator. As demonstrated in the above code, it will execute a command via the ssh (configured automatically by the SO). You could replace with with calls to a REST API or any other RPC method. You can also run code against the LXD container running the charm.

Building the charm

When you're ready, you can create your charm via the charm build command:

$ charm build
build: Composing into /home/stone/charms
build: Destination charm directory: /home/stone/charms/builds/pingpong
build: Please add a `repo` key to your layer.yaml, with a url from which your layer can be cloned.
build: Processing layer: layer:basic
build: Processing layer: layer:sshproxy
build: Processing layer: layer:vnfproxy
build: Processing layer: pingpong

This combines all layers that you included, and those that they include, into a charm called pingpong, located in the ~/charms/builds directory.

Updating VNF Descriptor

In your Virtual Network Function Descriptor (VNFD), you specify the name of the charm as demonstrated below:

vnfd-catalog:
    vnfd:
     -  id: ping_vnf
        name: ping_vnf
        vnf-configuration:
            juju:
                charm: pingpong

Then the compiled charm (from the builds directory) has to be packaged with the descriptor package under the charm directory. So the ping VNF with the charm would be:

ping_vnf
├── charms
│   └── pingpong
├── checksums.txt
├── icons
├── images
├── ping_vnfd.yaml
├── README
└── scripts