Day 1: VNF Services Initialization

Description of this phase

The objective of this section is to provide the guidelines to include all necessary elements in the VNF Package. This allows the exposed services inside the VNF to be automatically initialized right after the VNF instantiation.

The main mechanism to achieve this in OSM is to build a Charm and include it in the descriptor.

In the VNFD you will find metadata, which is declarative data specified in the YAML file, and code that takes care of the operations related to a VNF. The operations code is call “Charm”, and it can handle the lifecycle, configuration, integration, and actions/primitives in your workloads.

There are two kinds of Charms, and at this point you have to decide which one you need, and that depends on the nature of your workload. These are the two types of Charms:

  • Proxy Charms

  • Native Charms

If you are using a fixed image for your workload, which CANNOT be modified, then the Charm has to be allocated not in the workload (Proxy Charm). For those cases, the VCA (VNF Configuration and Abstraction) has an LXD and Kubernetes clouds available in which the Charm will live.

However, if the the workload CAN be modified, then the code can live in the same workload (Native Charm).

Besides charms, there is an experimental way of configuring network functions, powered by helm-based execution environments launched as PODs.

Using Juju-based or Helm-based execution environments

Adding Day-1 primitives to the descriptor

This type of initial actions will run automatically after instantiation and should be specified in the VNF descriptor. These can be defined at two different levels:

  • VDU-level: for a specific VDU, used when a VDU needs configuration, which is different than the VDU used for managing the VNF.

  • VNF-level: for the “management VDU”, used when the configuration applies to the VDU exposing a interface for managing the whole VNF.

Initial primitives must include a primitive named config that passes information for OSM VCA to be able to authenticate and run further primitives into the VNF. The config primitive should provide, at least, the following parameters:

  • ssh-hostname: Typically used with the <rw_mgmt_ip>, which is automatically replaced by the VNF or VDU management IP address specified in the correspondent section.

  • ssh-username: The username used for authentication with the VDU.

Additionally, OSM VCA needs the credentials to succeed the authentication. For that, there are two options:

  • Add ssh-password in the config initial-config-primitive: A static password

  • Add `config-access in the vnf/vdu-configuration: With this method, OSM will inject the public keys generated by the Proxy Charm to the workload.

    vnf-configuration:
        config-access:
            ssh-access:
            default-user: ubuntu
            required: true

NOTE: Any primitive can require a set of configuration parameters in a config.yaml file. The value for those parameters should be specified in the config initial primitive.

Additional to the config primitive, more initial primitives can be run in the desired order so that the VNF initializes its services. Note that each of these additional actions will be later detailed in the proxy charm that implements them.

The following example shows VNF-level initial primitives: both the expected config primitive in the beginning, but also the configure-remote and start-service to be run in addition right after initialization.

vnfd:
...
    df:
    - ...
    # VNF/VDU Configuration must use the ID of the VNF/VDU to be configured
    lcm-operations-configuration:
      operate-vnf-op-config:
        day1-2:
        -  id: vnf_id
           execution-environment-list:
           - id: configure-vnf
             connection-point-ref: vnf-mgmt
             juju:
               charm: samplecharm
           initial-config-primitive:
           -  execution-environment-ref: configure-vnf
              name: config
              parameter:
               -  name: ssh-hostname
                  value: <rw_mgmt_ip>
               -  name: ssh-username
                  value: admin
               -  name: ssh-password
                  value: secretpassword
               seq: '1'
           -  name: configure-remote
              parameter:
              -  name: dest-ip
                 value: 10.1.1.1
              seq: '2'
           -  name: start-service
              seq: '3'

Instantiation parameters can be used to define the values of these parameters in a later time, during the NS instantiation. Notice that the connection-point-ref can be used to map the primitive to any given VDU CP, enabling the posibility of having multiple primitives mapped to different management interfaces of different VDUs.

The values for the variables used at the primitive level are defined at instantiation time, just like in the cloud-init case:

osm ns-create ... --config "{additionalParamsForVnf: [{member-vnf-index: '1', additionalParams:{password: 'secretpassword', destination_ip: '10.1.1.1'}}]}"

Remember that when dealing with multiple variables, it might be useful to pass a YAML file instead.

osm ns-create ... --config-file vars.yaml

Creating Juju-based execution environments with Proxy Charms

DEPRECATED: Reactive (Method 1): Building a Proxy Charm the traditional way

a) Install the charm tools and setup your environment. You might want to copy the export lines to your “~/.bashrc” profile file to automatically load them in the next session.

snap install charm --classic
mkdir -p ~/charms/layers
export JUJU_REPOSITORY=~/charms
export LAYER_PATH=$JUJU_REPOSITORY/layers
cd $LAYER_PATH

b) A proxy charm includes, by default, the “VNF” and “basic” layers, which take care of the initial SSH connection to the VNF. Create the new personalized layer for your proxy charm:

charm create samplecharm
cd samplecharm

Note: Charm names do not support underscores.

c) Modify the basic files like this:

# layer.yaml file
includes:
    - layer:basic
    - layer:vnfproxy

# metadata.yaml file
name: samplecharm
summary: this is an example
maintainer: Gianpietro Lavado <gianpietro1@gmail.com>
description: |
  This is an example of a proxy charm deployed by Open Source Mano.
tags:
  - nfv
subordinate: false
series:
    - trusty
    - xenial

d) Create and modify the “actions.yaml” file, adding all actions/primitives and their parameters. Note that the value of these parameters are defined at the VNFD, either statically or by using variables, as explained earlier.

# actions.yaml file
configure-remote:
    description: "Configures the remote server"
    params:
        destination_ip:
            description: "IP of the remote server"
            type: string
            default: ""
start-service:
    description: "Starts the service of the VNF"

e) Create an “actions” folder and populate it with files representing each action. Filenames should match the name of the primitive, should be made executable with chmod +x and all must contain the following exact content.

# actions/configure-remote and actions/start-service files
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

f) Open the respective file at the ‘reactive/’ folder. This will be used to code, in Python, the actual actions that will run through SSH when each primitive is triggered. Note that any variable can be recovered in two ways:

  • Using the config() function if the variable belongs to that specific primitive.

  • Using the action_get('name-of-parameter') function to get any other parameter.

The following example provides an idea of the contents of a reactive file.

# reactive/samplecharm.py file

# dependencies that might be needed
from charmhelpers.core.hookenv import (
    action_get,
    action_fail,
    action_set,
    config,
    status_set,
)

from charms.reactive import (
    remove_state as remove_flag,
    set_state as set_flag,
    when, when_not
)

import charms.sshproxy

# Your actions here
@when('actions.configure-remote')
def configure_remote():
    err = ''
    # Variables should be retrieved, if needed
    cfg = config()
    mgmt_ip = cfg['ssh-hostname']
    destination_ip = action_get('dest-ip')
    try:
        # Commands to be run through SSH should go here
        cmd = "vnfcli set license " + mgmt_ip + " server " + destination_ip
        result, err = charms.sshproxy._run(cmd)
    except:
        action_fail('command failed:' + err)
    else:
        action_set({'output': result})
    finally:
        remove_flag('actions.configure-remote')

@when('actions.start-service')
def start_service():
    err = ''
    # Variables should be retrieved, if needed
    try:
        # Commands to be run through SSH should go here
        cmd = "sudo service vnfoper start"
        result, err = charms.sshproxy._run(cmd)
    except:
        action_fail('command failed:' + err)
    else:
        action_set({'output': result})
    finally:
        remove_flag('actions.start-service')

g) If your proxy charm layer needs some extra dependencies, the debian or pip package should be added to the layer.yaml file. This is done through the ‘packages’ and ‘python_packages’ options inside the layer, for example:

includes:
- "layer:basic"
- "layer:ansible-base"
- "layer:vnfproxy"
options:
  basic:
    use_venv: false
    packages: ["build-essential","libssl-dev"]
    python_packages: ["pyyaml"]

h) Finally, build the charm with charm build and copy the resulting folder (in this case the ~/charms/builds/simplecharm directory) inside the charms folder of your VNF Package.

Futher information about building charms can be found here.

DEPRECATED: Reactive (Method 2): Using Proxy Charm Generators

To date, the only supported generator is Ansible, which means that a Proxy Charm can be automatically populated based on an Ansible Playbook.

A sample Ansible playbook would look like this:

# This sample playbook applies to a VyOS router
# The hosts where the playbook will be executed will automatically contain the IP address of the VDU, in a /etc/ansible/hosts file at the charm container
- hosts: vyos-routers
  # note that the following setting is needed on most cases
  connection: local
  tasks:
  - name: configure the remote device
    vyos_config:
      lines:
        - set nat destination rule 1 inbound-interface eth0
        - set nat destination rule 1 destination port 80
        - set nat destination rule 1 protocol tcp
        - set nat destination rule 1 translation address {{dest_ip}}

Once the Ansible playbook has been tested against your VNF, the procedure to incorporate it in a charm is as follows:

a) Create your environment and charm in the traditional way (that is, steps (a) and (b) from the previous method)

b) Clone the devops repository elsewhere and copy the generator files to your charm root folder. For example: cp -r ~/devops/descriptor-packages/tools/charm-generator/* ./ [TODO: migrate to binary]

c) Install the dependencies of the generator with sudo pip3 install -r requirements.txt

d) Run the generator, which will populate all the required files automatically. It requires an Ansible playbook to be copied inside a new “playbooks” folder under the charm root directory, and the primitive to be named “playbook” (by default). The following example runs the generator with the minimal options.

python3 generator-runner.py --ansible --summary "Configures VNF using Ansible" \
--maintainer "Gianpietro Lavado <glavado@whitestack.com>" \
--description "Configures VNF using Ansible"

e) Adjust the “reactive” file as desired, for example, if you wish to pass some parameters to your playbook (which supports Jinja)

@when('actions.playbook')
def playbook():
    try:

        # getting a variables from the config primitive
        cfg = config()
        mgmt_ip = cfg['ssh-hostname']
        # a sample on passing a specific file to the playbook, considering that the charm will be located at the /var/lib/juju/agents folder
        config_file = charms.libansible.find('config.conf', '/var/lib/juju/agents/')

        # populating an object with the variables
        dict_vars = {'dest_ip': mgmt_ip,'config_file': config_file}

        # running the playbook along with the given variables
        result = charms.libansible.execute_playbook('playbook.yaml', dict_vars)
    except:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        err = traceback.format_exception(exc_type, exc_value, exc_traceback)
        action_fail('playbook failed: ' + str(err))
    else:
        action_set({'output': result})
    finally:
        remove_flag('actions.playbook')

f) Finally, build the charm with charm build and copy the resulting folder (in this case the “~/charms/builds/simplecharm” directory) inside the “charms” folder of your VNF Package.

Once the VNF is launched, the results from running the generator will be found inside the proxy charm lxc container, at the “/var/log/ansible.log” file. If not successful, it could indicate the need for other possible modifications which are applicable for certain VNFs.

Note: some VNFs will not pass some SSH pre-checks that Ansible performs in some operations (SFTP, SCP, etc.) In those cases, it has been noted that ansible_connection=ssh, which is a default set of the generator, needs to be disabled. This preset would need to be deleted from the lib/charms/libansible.py file, create_hosts function. [TODO: explore an enhancement to the Ansible Generator, to be as generic as possible]

Scaling proxy charms

In the additional configuration parameters, there is a config-units option that will scale the Proxy Charms you want.

additionalParamsForVnf:
  - member-vnf-index: "1"
    config-units: 2

Creating Helm-based execution environments

As of OSM version 8, NF configurations can also be done with Helm-based execution environments, which deploy a pair of extra pods in the OSM K8s namespace when included. These PODs follow the VNF lifecycle (as charms do) and can be also used to collect indicators as explained in the Day 2 section.

At the VNF package level:

  • The only file that needs to be modified before building it is the vnf_ee.py file at the helm-charts/chart_name/source/ folder, just as in this sample VNF Package, where the chart is called eechart.

  • The file contains the primitives, in this case, the touch primitive, that creates a file in the NF as a quick example.

  • The rest of the structure inside the helm-chart folder shown in the example above needs to be included.

Once the primitives have been defined and included in the vnf_ee.py file, the descriptor needs to specify the helm-based day-1 primitives that will be launched. For example:

vnfd:
  ...
  df:
  - ...
    lcm-operations-configuration:
      operate-vnf-op-config:
        day1-2:
        - id: simple_ee-vnf
          config-access:
            ssh-access:
              default-user: ubuntu
              required: true
          execution-environment-list:
          - external-connection-point-ref: vnf-mgmt-ext
            helm-chart: eechart
            id: monitor
          initial-config-primitive:
          - execution-environment-ref: monitor
            name: config
            parameter:
            - name: ssh-hostname
              value: <rw_mgmt_ip>
            - name: ssh-username
              value: ubuntu
            - name: ssh-password
              value: osm2020
            seq: '1'
          - execution-environment-ref: monitor
            name: touch
            parameter:
            - name: file-path
              value: /home/ubuntu/first-touch
            seq: '2'

Testing Instantiation of the VNF Package

Remember the objective of this phase: to configure the VNF automatically so it starts providing the expected service.

To test this out, the NS can be launched using the OSM client, like this:

osm ns-create --ns_name [ns name] --nsd_name [nsd name] --vim_account [vim name] --ssh_keys [comma separated list of public key files to inject to vnfs]

Furthermore, and as mentioned earlier, extra instantiation parameters can be passed so that the VNF can be adapted to the particular instantiation environment or to achieve a proper inter-operation with other VNFs into the specific NS.

For example, if using IP Profiles to predefine subnet values, a specific IP address could be passed to an interface like this:

osm ns-create ... --config '{vnf: [ {member-vnf-index: "1", internal-vld: [ {name: internal, ip-profile: {...}, internal-connection-point: [{id-ref: id1, ip-address: "a.b.c.d"}] ] } ],
    additionalParamsForVnf...}'

When dealing with multiple fixed IP addresses, variables or other additions to the original descriptor, it might be useful to pass a YAML file instead.

osm ns-create ... --config-file ip-vars.yaml

As you can see, the parameters being defined at instantiation time follow the information model structure. Further information and examples about these parameters can be reviewed here.

After deployment is done, proxy charms can be monitored and debugged by using the juju status and juju debug-log commands, respectively.

Since Release 6, every NS has its own juju instance (model) for its charms, so before running juju commands, you need to switch to the right model using juju switch [NS ID]

If proxy charms need to be started at any particular order, please note that the order of proxy charm initialization follows the order in which ‘constituent VNFs’ are listed at the NSD, but the actual operations could be executed in a different order, depending on the time it takes for each proxy charm container to be ready.