# Creating your own VNF charm

## Creating a VNF proxy charm

### What is a charm

A [charm](https://jujucharms.com/docs/stable/charms) is a collection of scripts and metadata that encapsulate the distilled DevOps knowledge of experts in a particular product. These charms make it easy to reliably and repeatedly deploy applications, then scale them as required with minimal effort.

Driven by [Juju](https://jujucharms.com/docs/stable/about-juju), these charms manage the complete lifecycle of the application, including installation, configuration, clustering, and scaling.

### What is a proxy charm

OSM Release THREE supports a limited version of charms that we call "proxy charms". These charms is responsible for doing Day 1 configuration. Configurations are mapped to [Juju Actions](https://jujucharms.com/docs/stable/actions) which manage configuration within the VNFD qcow2 image (over SSH, via RESTful API, etc).

The diagram below illustrates the OSM workflow:

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

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.

### Creating a proxy charm

#### Setup

We recommend that you are running Ubuntu 16.04 or newer, or [install snapd](https://docs.snapcraft.io/core/install) on the Linux distribution of your choice.

Install the *charm* snap, which provides the charm command and libraries necessary to compile your charm:

```
snap install charm
```

Setup your workspace for writing layers and building charms:

```
mkdir -p ~/charms/layers
export JUJU_REPOSITORY=~/charms
export LAYER_PATH=$JUJU_REPOSITORY/layers
cd $LAYER_PATH
```

#### Layers

Layers are individual components that, when combined, result in a finished product. The diagram below describes what our example *pingpong* charm looks like, followed by a walkthrough of how it is built. The completed charm is available in the [juju-charms](https://osm.etsi.org/gitweb/?p=osm/juju-charms.git;a=summary) repository.

```
+------------------+
|                  |
|      Layers      |
|                  |
|  +------------+  |
|  |            |  |
|  |    Base    |  |
|  |            |  |
|  +------+-----+  |
|         |        |
|  +------v-----+  |
|  |            |  |
|  |  sshproxy  |  |            +-----------------+
|  |            |  |            |                 |
|  +------+-----+  |            |     pingpong    |
|         |        +------------>                 |
|  +------v-----+  |            |      charm      |
|  |            |  |            |                 |
|  |  vnfproxy  |  |            +-----------------+
|  |            |  |
|  +------+-----+  |
|         |        |
|  +------v-----+  |
|  |            |  |
|  |  pingpong  |  |
|  |            |  |
|  +------------+  |
|                  |
+------------------+
```

Create the layer for your proxy charm:

```
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
```

Next, modify *layers.yaml* to the following:

```
includes:
    - layer:basic
    - layer:vnfproxy
```

The *[metadata.yaml](https://jujucharms.com/docs/stable/authors-charm-metadata)* file describes what your charm is and sets certain properties used by Juju.

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


This means that your charm will include the basic layer, required for all charms, and the vnfproxy layer, which has been designed to aid in the development in proxy charms by implementing common functionality.

#### Actions

There are three pieces that make up an action: *actions.yaml*, which define an action, the *actions/* directory where we'll place a small script that invokes the reactive framework, and the python code in *reactive/pingpong.py* that performs said action.

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"
```



```
mkdir actions/
```

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.

```
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
```

After this, make the file executable.

```
chmod +x actions/set-server
```

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

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.

#### VNF Descriptor

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

```
vnfd:vnfd-catalog:
    vnfd:vnfd:
     -  vnfd:id: rift_pong_vnf
        vnfd:name: pong_vnf
        vnfd:vnf-configuration:
            vnfd:juju:
                vnfd: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
```
