--- /dev/null
+BUILD_DIR = build
+
+NSDS := gw_corpa_ns ims_allin1_corpa mwc16_gen_ns mwc16_pe_ns
+NSD_SRC_DIR := src/nsd
+NSD_BUILD_DIR := $(BUILD_DIR)/nsd
+
+NSD_SRC_DIRS := $(addprefix $(NSD_SRC_DIR)/, $(NSDS))
+NSD_BUILD_DIRS := $(addprefix $(NSD_BUILD_DIR)/, $(NSDS))
+NSD_PKGS := $(addsuffix .tar.gz, $(NSDS))
+NSD_BUILD_PKGS := $(addprefix $(NSD_BUILD_DIR)_pkgs/, $(NSD_PKGS))
+
+VNFDS := 6wind_vnf gw_corpa_pe1_vnf gw_corpa_pe2_vnf ims_allin1_2p_vnf tidgen_mwc16_vnf
+VNFD_SRC_DIR := src/vnfd
+VNFD_BUILD_DIR := $(BUILD_DIR)/vnfd
+
+VNFD_SRC_DIRS := $(addprefix $(VNFD_SRC_DIR)/, $(VNFDS))
+VNFD_BUILD_DIRS := $(addprefix $(VNFD_BUILD_DIR)/, $(VNFDS))
+VNFD_PKGS := $(addsuffix .tar.gz, $(VNFDS))
+VNFD_BUILD_PKGS := $(addprefix $(VNFD_BUILD_DIR)_pkgs/, $(VNFD_PKGS))
+
+all: $(VNFD_BUILD_PKGS) ${NSD_BUILD_PKGS}
+ echo $@
+
+clean:
+ -@ $(RM) -rf $(BUILD_DIR)
+
+$(VNFD_BUILD_DIR)/%: $(VNFD_SRC_DIR)/%
+ mkdir -p $(VNFD_BUILD_DIR)
+ cp -rf $< $(VNFD_BUILD_DIR)
+
+ src/gen_vnfd_pkg.sh $< $@
+ src/generate_descriptor_pkg.sh $(BUILD_DIR)/vnfd_pkgs $@
+
+$(NSD_BUILD_DIR)/%: $(NSD_SRC_DIR)/%
+ mkdir -p $(NSD_BUILD_DIR)
+ cp -rf $< $(NSD_BUILD_DIR)
+
+ src/gen_nsd_pkg.sh $< $@
+
+$(BUILD_DIR)/nsd_pkgs/%.tar.gz: $(NSD_BUILD_DIR)/%
+ src/generate_descriptor_pkg.sh $(BUILD_DIR)/nsd_pkgs $<
+
+$(BUILD_DIR)/vnfd_pkgs/%.tar.gz: $(VNFD_BUILD_DIR)/%
+ src/generate_descriptor_pkg.sh $(BUILD_DIR)/vnfd_pkgs $<
--- /dev/null
+#!/bin/bash
+
+# Generates a NSD descriptor package from a source directory
+# Usage:
+# gen_nsd_pkg.sh <pkg_src_dir> <pkg_dest_dir>
+
+set -o nounset
+
+if [ $# -ne 2 ]; then
+ echo "Error: Must provide 2 parameters" >@2
+ exit 1
+fi
+
+pkg_src_dir="$1"
+pkg_dest_dir="$2"
+
+if [ ! -e ${pkg_src_dir} ]; then
+ echo "Error: ${pkg_src_dir} does not exist"
+ exit 1
+fi
+
+if [ ! -e ${pkg_dest_dir} ]; then
+ echo "Error: ${pkg_src_dir} does not exist"
+ exit 1
+fi
+
+echo "Generating package in directory: ${pkg_dest_dir}"
+
+# Create any missing directories/files so each package has
+# a complete hierachy
+nsd_dirs=( ns_config vnf_config icons scripts )
+nsd_files=( README )
+
+nsd_dir="${pkg_src_dir}"
+echo $(pwd)
+
+mkdir -p "${pkg_dest_dir}"
+cp -rf ${nsd_dir}/* "${pkg_dest_dir}"
+for sub_dir in ${nsd_dirs[@]}; do
+ dir_path=${pkg_dest_dir}/${sub_dir}
+ mkdir -p ${dir_path}
+done
+
+for file in ${nsd_files[@]}; do
+ file_path=${pkg_dest_dir}/${file}
+ touch ${file_path}
+done
--- /dev/null
+#!/bin/bash
+
+# Generates a NSD descriptor package from a source directory
+# Usage:
+# gen_vnfd_pkg.sh <pkg_src_dir> <pkg_dest_dir>
+
+set -o nounset
+
+if [ $# -ne 2 ]; then
+ echo "Error: Must provide 2 parameters" >@2
+ exit 1
+fi
+
+pkg_src_dir="$1"
+pkg_dest_dir="$2"
+
+if [ ! -e ${pkg_src_dir} ]; then
+ echo "Error: ${pkg_src_dir} does not exist"
+ exit 1
+fi
+
+if [ ! -e ${pkg_dest_dir} ]; then
+ echo "Error: ${pkg_src_dir} does not exist"
+ exit 1
+fi
+
+echo "Generating package in directory: ${pkg_dest_dir}"
+
+# Create any missing directories/files so each package has
+# a complete hierachy
+vnfd_dirs=( charms icons scripts images )
+vnfd_files=( README )
+
+vnfd_dir="${pkg_src_dir}"
+echo $(pwd)
+
+mkdir -p "${pkg_dest_dir}"
+cp -rf ${vnfd_dir}/* "${pkg_dest_dir}"
+for sub_dir in ${vnfd_dirs[@]}; do
+ dir_path=${pkg_dest_dir}/${sub_dir}
+ mkdir -p ${dir_path}
+done
+
+for file in ${vnfd_files[@]}; do
+ file_path=${pkg_dest_dir}/${file}
+ touch ${file_path}
+done
--- /dev/null
+#! /usr/bin/bash
+# STANDARD_RIFT_IO_COPYRIGHT
+# Author(s): Anil Gunturu
+# Creation Date: 2015/10/09
+#
+# This shell script is used to create a descriptor package
+# The main functions of this script include:
+# - Generate checksums.txt file
+# - Generate a tar.gz file
+
+# Usage: generate_descriptor_pkg.sh <package-directory> <dest_package_dir>
+
+mkdir -p $1
+mkdir -p $2
+
+dest_dir=$(cd $1 && pwd)
+pkg_dir=$(cd $2 && pwd)
+
+echo $(pwd)
+cd ${pkg_dir}
+rm -rf checksums.txt
+find * -type f |
+ while read file; do
+ md5sum $file >> checksums.txt
+ done
+cd ..
+tar -zcvf ${dest_dir}/$(basename $pkg_dir).tar.gz $(basename $pkg_dir)
--- /dev/null
+nsd:nsd-catalog:
+ nsd:
+ - id: gw_corpA
+ name: gw_corpA
+ short-name: gw_corpA
+ description: Gateways to access as corpA to PE1 and PE2
+ logo: osm_2x.png
+ constituent-vnfd:
+ - member-vnf-index: '1'
+ vnfd-id-ref: gw_corpA_PE1
+ - member-vnf-index: '2'
+ vnfd-id-ref: gw_corpA_PE2
+ vld:
+ - id: connection_0
+ name: connection_0
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mgmt
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth1
+ vnfd-id-ref: gw_corpA_PE1
+ - id: connection_1
+ name: connection_1
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mgmt
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: eth1
+ vnfd-id-ref: gw_corpA_PE2
+ - id: connection_2
+ name: connection_2
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc1
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: gw_corpA_PE1
+ - id: connection_3
+ name: connection_3
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc2
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: gw_corpA_PE2
+ - id: connection_4
+ name: connection_4
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data1
+ segmentation_id: '101'
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: gw_corpA_PE1
+ - id: connection_5
+ name: connection_5
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data2
+ segmentation_id: '102'
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: gw_corpA_PE2
--- /dev/null
+nsd:nsd-catalog:
+ nsd:
+ - id: IMS-corpA
+ name: IMS-corpA
+ short-name: IMS-corpA
+ description: All in one Clearwater IMS for corporation A in MWC16
+ logo: osm_2x.png
+ constituent-vnfd:
+ - member-vnf-index: '1'
+ vnfd-id-ref: IMS-ALLIN1_2p
+ vld:
+ - id: data
+ name: data
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: net-corp
+ segmentation_id: '108'
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: IMS-ALLIN1_2p
+ - id: management
+ name: management
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: net-mgmtOS
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth1
+ vnfd-id-ref: IMS-ALLIN1_2p
+ config-primitive:
+ - name: Update Domain
+ vnf-primitive-group:
+ - member-vnf-index-ref: '1'
+ vnfd-id-ref: IMS-ALLIN1_2p
+ vnfd-name: cwims_vnfd
+ primitive:
+ - index: '1'
+ name: config
+ - name: Add User
+ vnf-primitive-group:
+ - member-vnf-index-ref: '1'
+ vnfd-id-ref: IMS-ALLIN1_2p
+ vnfd-name: cwims_vnfd
+ primitive:
+ - index: '1'
+ name: create-update-user
+ - name: Delete User
+ vnf-primitive-group:
+ - member-vnf-index-ref: '1'
+ vnfd-id-ref: IMS-ALLIN1_2p
+ vnfd-name: cwims_vnfd
+ primitive:
+ - index: '1'
+ name: delete-user
--- /dev/null
+initial_config_primitive:
+- name: config
+ parameter:
+ proxied_ip: <rw_mgmt_ip>
--- /dev/null
+nsd:nsd-catalog:
+ nsd:
+ - id: mwc16_traffic_generator
+ name: mwc16_traffic_generator
+ short-name: mwc16_traffic_generator
+ description: Traffic generator connected to the demo environment
+ logo: osm_2x.png
+ constituent-vnfd:
+ - member-vnf-index: '1'
+ vnfd-id-ref: mwc16gen
+ vld:
+ - id: connection 0
+ name: connection 0
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: mwc16gen
+ - id: connection 1
+ name: connection 1
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mgmt
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth1
+ vnfd-id-ref: mwc16gen
+ - id: connection 2
+ name: connection 2
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data1
+ segmentation_id: '3000'
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: mwc16gen
+ - id: connection 3
+ name: connection 3
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data2
+ segmentation_id: '3000'
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe2
+ vnfd-id-ref: mwc16gen
--- /dev/null
+nsd:nsd-catalog:
+ nsd:
+ - id: mwc16-pe
+ name: mwc16-pe
+ short-name: mwc16-pe
+ description: mwc16-pe
+ logo: osm_2x.png
+ constituent-vnfd:
+ - member-vnf-index: '1'
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index: '2'
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index: '3'
+ vnfd-id-ref: 6WindTR1.1.2
+ vld:
+ - id: 6WindTR1.1.2__3 to OpenStack
+ name: 6WindTR1.1.2__3 to OpenStack
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: interDC
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '3'
+ vnfd-connection-point-ref: xe3
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: 6WindTR1.1.2__1 enty point
+ name: 6WindTR1.1.2__1 enty point
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data1
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe2
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: 6WindTR1.1.2__2 entry point
+ name: 6WindTR1.1.2__2 entry point
+ type: ELAN
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mwc16data2
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: xe2
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: management
+ name: management
+ provider-network:
+ overlay-type: VLAN
+ physical-network: mgmt
+ type: ELAN
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index-ref: '3'
+ vnfd-connection-point-ref: eth0
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: 6WindTR1.1.2__2-6WindTR1.1.2__3
+ name: 6WindTR1.1.2__2-6WindTR1.1.2__3
+ type: ELAN
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: xe1
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index-ref: '3'
+ vnfd-connection-point-ref: xe1
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: 6WindTR1.1.2__1-6WindTR1.1.2__3
+ name: 6WindTR1.1.2__1-6WindTR1.1.2__3
+ type: ELAN
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe1
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index-ref: '3'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: 6WindTR1.1.2
+ - id: 6WindTR1.1.2__1-6WindTR1.1.2__2
+ name: 6WindTR1.1.2__1-6WindTR1.1.2__2
+ type: ELAN
+ vnfd-connection-point-ref:
+ - member-vnf-index-ref: '1'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: 6WindTR1.1.2
+ - member-vnf-index-ref: '2'
+ vnfd-connection-point-ref: xe0
+ vnfd-id-ref: 6WindTR1.1.2
+ config-primitive:
+ - name: Add SP Test Corporation
+ parameter:
+ - name: Corporation Name
+ data-type: string
+ default-value: SP Test Corp
+ mandatory: 'true'
+ - name: Tunnel Key
+ data-type: integer
+ default-value: '10'
+ hidden: 'true'
+ mandatory: 'true'
+ parameter-group:
+ - mandatory: 'false'
+ name: PE1
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '3000'
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth3
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.1.0/24
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.1.1
+ hidden: 'true'
+ mandatory: 'true'
+ - mandatory: 'false'
+ name: PE2
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '3000'
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth3
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.2.0/24
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.2.1
+ hidden: 'true'
+ mandatory: 'true'
+ - mandatory: 'false'
+ name: PE3
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '3000'
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth3
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.3.0/24
+ hidden: 'true'
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.3.1
+ hidden: 'true'
+ mandatory: 'true'
+ user-defined-script: add_corporation.py
+ - name: Add Corporation
+ parameter:
+ - name: Corporation Name
+ data-type: string
+ default-value: CorpA
+ mandatory: 'true'
+ - name: Tunnel Key
+ data-type: integer
+ default-value: '1'
+ hidden: 'true'
+ mandatory: 'true'
+ parameter-group:
+ - mandatory: 'false'
+ name: PE1
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '101'
+ mandatory: 'true'
+ read-only: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth3
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.1.0/24
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.1.1
+ mandatory: 'true'
+ - mandatory: 'false'
+ name: PE2
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '102'
+ mandatory: 'true'
+ read-only: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth3
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.2.0/24
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.2.1
+ mandatory: 'true'
+ - mandatory: 'false'
+ name: PE3
+ parameter:
+ - name: Vlan ID
+ data-type: integer
+ default-value: '108'
+ mandatory: 'true'
+ read-only: 'true'
+ - name: Interface Name
+ data-type: string
+ default-value: eth4
+ mandatory: 'true'
+ - name: Corp. Network
+ data-type: string
+ default-value: 10.0.4.0/24
+ mandatory: 'true'
+ - name: Corp. Gateway
+ data-type: string
+ default-value: 10.0.4.1
+ mandatory: 'true'
+ user-defined-script: add_corporation.py
--- /dev/null
+Add Corporation:
+ parameter:
+ Tunnel Key: '1'
+ Corporation Name: 'CorpA'
+ parameter_group:
+ PE1:
+ Corp. Gateway: 10.0.1.1
+ Corp. Network: 10.0.1.0/24
+ Interface Name: eth3
+ Vlan ID: '101'
+ PE2:
+ Corp. Gateway: 10.0.2.1
+ Corp. Network: 10.0.2.0/24
+ Interface Name: eth3
+ Vlan ID: '102'
+ PE3:
+ Corp. Gateway: 10.0.4.1
+ Corp. Network: 10.0.4.0/24
+ Interface Name: eth4
+ Vlan ID: '108'
+Add SP Test Corporation:
+ parameter:
+ Tunnel Key: '10'
+ Corporation Name: 'SP Test Corp'
+ parameter_group:
+ PE1:
+ Corp. Gateway: 10.0.1.1
+ Corp. Network: 10.0.1.0/24
+ Interface Name: eth3
+ Vlan ID: '3000'
+ PE2:
+ Corp. Gateway: 10.0.2.1
+ Corp. Network: 10.0.2.0/24
+ Interface Name: eth3
+ Vlan ID: '3000'
+ PE3:
+ Corp. Gateway: 10.0.3.1
+ Corp. Network: 10.0.3.0/24
+ Interface Name: eth3
+ Vlan ID: '3000'
--- /dev/null
+#!/usr/bin/env python3
+import argparse
+import hashlib
+import ipaddress
+import itertools
+import jujuclient
+import logging
+import sys
+import time
+import yaml
+
+
+logging.basicConfig(filename="/tmp/rift_ns_add_corp.log", level=logging.DEBUG)
+logger = logging.getLogger()
+
+ch = logging.StreamHandler()
+ch.setLevel(logging.INFO)
+
+# create formatter and add it to the handlers
+formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ch.setFormatter(formatter)
+logger.addHandler(ch)
+
+
+dry_run = False
+
+class JujuActionError(Exception):
+ pass
+
+
+class JujuClient(object):
+ """Class for executing Juju actions """
+ def __init__(self, ip, port, user, passwd):
+ self._ip = ip
+ self._port = port
+ self._user = user
+ self._passwd = passwd
+
+ endpoint = 'wss://%s:%d' % (ip, port)
+ logger.debug("Using endpoint=%s", endpoint)
+ if dry_run:
+ return
+ self.env = jujuclient.Environment(endpoint)
+ self.env.login(passwd, user)
+
+ def get_service(self, name):
+ return self.env.get_service(name)
+
+ def _get_units(self, name):
+ """
+ Get the units associated with service
+ """
+ units = self.env.status(name)['Services'][name]['Units']
+ units = list(units.keys())
+
+ # convert to a friendly format for juju-python-client
+ units[:] = [('unit-%s' % u).replace('/', '-') for u in units]
+ return units
+
+ def exec_action(self, name, action_name, params, block=False):
+ logger.debug("execute actiion %s using params %s", action_name, params)
+ if dry_run:
+ return
+
+ actions = jujuclient.Actions(self.env)
+ results = actions.enqueue_units(self._get_units(name),
+ action_name,
+ params)
+ if not block:
+ return results
+
+ if 'error' in results['results'][0].keys():
+ raise JujuActionError("Juju action error: %s" % results['results'][0])
+
+ action = results['results'][0]['action']
+ info = actions.info([action])
+ i = 0
+ logging.debug("Initial action results: %s", results['results'][0])
+ while info['results'][0]['status'] not in ['completed', 'failed']:
+ time.sleep(1)
+ info = actions.info([action])
+
+ # break out if the action doesn't complete in 10 secs
+ i += 1
+ if i == 10:
+ raise JujuActionError("Juju action timed out after 30 seconds")
+
+ if info['results'][0]['status'] != 'completed':
+ raise JujuActionError("Action %s failure: %s" % (action_name, info['results'][0]))
+
+ return info
+
+
+class CharmAction(object):
+ def __init__(self, deployed_name, action_name, action_params=None):
+ self._deployed_name = deployed_name
+ self._action_name = action_name
+ self._params = action_params if action_params is not None else []
+
+ def execute(self, juju_client):
+ logger.info("Executing charm (%s) action (%s) with params (%s)",
+ self._deployed_name, self._action_name, self._params)
+ try:
+ info = juju_client.exec_action(
+ name=self._deployed_name,
+ action_name=self._action_name,
+ params=self._params,
+ block=True
+ )
+
+ except JujuActionError as e:
+ logger.error("Juju charm (%s) action (%s) failed: %s",
+ self._deployed_name, self._action_name, str(e))
+ raise
+
+ logger.debug("Juju charm (%s) action (%s) success.",
+ self._deployed_name, self._action_name)
+
+
+class DeployedProxyCharm(object):
+ def __init__(self, juju_client, service_name, mgmt_ip=None, charm_name=None):
+ self._juju_client = juju_client
+ self.service_name = service_name
+ self.mgmt_ip = mgmt_ip
+ self.charm_name = charm_name
+
+ def do_action(self, action_name, action_params={}):
+ action = CharmAction(self.service_name, action_name, action_params)
+ action.execute(self._juju_client)
+
+
+class SixWindPEProxyCharm(DeployedProxyCharm):
+ USER = "root"
+ PASSWD = "6windos"
+
+ def configure_interface(self, iface_name, ipv4_interface_str=None):
+ action = "configure-interface"
+ params = {'iface-name', iface_name}
+
+ if ipv4_interface_str is None:
+ # Use ipaddress module to validate ipv4 interface string
+ ip_intf = ipaddress.IPv4Interface(ipv4_interface_str)
+ params["cidr"] = ip_intf.with_prefixlen
+
+ self.do_action(action, params)
+ else:
+ self.do_action(action, params)
+
+
+ def add_corporation(self, domain_name, user_iface_name, vlan_id, corp_gw,
+ corp_net, local_net="10.255.255.0/24", local_net_area="0"):
+ logger.debug("Add corporation called with params: %s", locals())
+
+ action = "add-corporation"
+ params = {
+ "domain-name": domain_name,
+ "iface-name": user_iface_name,
+ "vlan-id": int(vlan_id),
+ "cidr": corp_net,
+ "area": corp_gw,
+ "subnet-cidr":local_net,
+ "subnet-area":local_net_area,
+ }
+
+ self.do_action(action, params)
+
+ def connect_domains(self, domain_name, core_iface_name, local_ip, remote_ip,
+ internal_local_ip, internal_remote_ip, tunnel_name,
+ tunnel_key, tunnel_type="gre"):
+
+ logger.debug("Connect domains called with params: %s", locals())
+
+ action = "connect-domains"
+ params = {
+ "domain-name": domain_name,
+ "iface-name": core_iface_name,
+ "tunnel-name": tunnel_name,
+ "local-ip": local_ip,
+ "remote-ip": remote_ip,
+ "tunnel-key": tunnel_key,
+ "internal-local-ip": internal_local_ip,
+ "internal-remote-ip": internal_remote_ip,
+ "tunnel-type":tunnel_type,
+ }
+
+ self.do_action(action, params)
+
+
+class PEGroupConfig(object):
+ def __init__(self, pe_group_cfg):
+ self._pe_group_cfg = pe_group_cfg
+
+ def _get_param_value(self, param_name):
+ for param in self._pe_group_cfg["parameter"]:
+ if param["name"] == param_name:
+ return param["value"]
+
+ raise ValueError("PE param not found: %s" % param_name)
+
+ @property
+ def vlan_id(self):
+ return self._get_param_value("Vlan ID")
+
+ @property
+ def interface_name(self):
+ return self._get_param_value("Interface Name")
+
+ @property
+ def corp_network(self):
+ return self._get_param_value("Corp. Network")
+
+ @property
+ def corp_gateway(self):
+ return self._get_param_value("Corp. Gateway")
+
+
+class AddCorporationRequest(object):
+ def __init__(self, add_corporation_rpc):
+ self._add_corporation_rpc = add_corporation_rpc
+
+ @property
+ def name(self):
+ return self._add_corporation_rpc["name"]
+
+ @property
+ def param_groups(self):
+ return self._add_corporation_rpc["parameter_group"]
+
+ @property
+ def params(self):
+ return self._add_corporation_rpc["parameter"]
+
+ @property
+ def corporation_name(self):
+ for param in self.params:
+ if param["name"] == "Corporation Name":
+ return param["value"]
+
+ raise ValueError("Could not find 'Corporation Name' field")
+
+ @property
+ def tunnel_key(self):
+ for param in self.params:
+ if param["name"] == "Tunnel Key":
+ return param["value"]
+
+ raise ValueError("Could not find 'Tunnel Key' field")
+
+ def get_pe_parameter_group_map(self):
+ group_name_map = {}
+ for group in self.param_groups:
+ group_name_map[group["name"]] = group
+
+ return group_name_map
+
+ def get_parameter_name_map(self):
+ name_param_map = {}
+ for param in self.params:
+ name_param_map[param["name"]] = param
+
+ return name_param_map
+
+ @classmethod
+ def from_yaml_cfg(cls, yaml_hdl):
+ config = yaml.load(yaml_hdl)
+ return cls(
+ config["rpc_ip"],
+ )
+
+
+class JujuVNFConfig(object):
+ def __init__(self, vnfr_index_map, vnf_name_map, vnf_init_config_map):
+ self._vnfr_index_map = vnfr_index_map
+ self._vnf_name_map = vnf_name_map
+ self._vnf_init_config_map = vnf_name_map
+
+ def get_service_name(self, vnf_index):
+ for vnfr_id, index in self._vnfr_index_map.items():
+ if index != vnf_index:
+ continue
+
+ return self._vnf_name_map[vnfr_id]
+
+ raise ValueError("VNF Index not found: %s" % vnf_index)
+
+ def get_vnfr_id(self, vnf_index):
+ for vnfr_id, index in self._vnfr_index_map.items():
+ if index != vnf_index:
+ continue
+
+ return vnfr_id
+
+ raise ValueError("VNF Index not found: %s" % vnf_index)
+
+ @classmethod
+ def from_yaml_cfg(cls, yaml_hdl):
+ config = yaml.load(yaml_hdl)
+ return cls(
+ config["vnfr_index_map"],
+ config["unit_names"],
+ config["init_config"],
+ )
+
+
+class JujuClientConfig(object):
+ def __init__(self, juju_ctrl_cfg):
+ self._juju_ctrl_cfg = juju_ctrl_cfg
+
+ @property
+ def name(self):
+ return self._juju_ctrl_cfg["name"]
+
+ @property
+ def host(self):
+ return self._juju_ctrl_cfg["host"]
+
+ @property
+ def port(self):
+ return self._juju_ctrl_cfg["port"]
+
+ @property
+ def user(self):
+ return self._juju_ctrl_cfg["user"]
+
+ @property
+ def secret(self):
+ return self._juju_ctrl_cfg["secret"]
+
+ @classmethod
+ def from_yaml_cfg(cls, yaml_hdl):
+ config = yaml.load(yaml_hdl)
+ return cls(
+ config["config_agent"],
+ )
+
+
+class OSM_MWC_Demo(object):
+ VNF_INDEX_NAME_MAP = {
+ "PE1": 1,
+ "PE2": 2,
+ "PE3": 3,
+ }
+
+ CORE_PE_CONN_MAP = {
+ "PE1": {
+ "PE2": {
+ "ifacename": "eth1",
+ "ip": "10.10.10.9",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.1"
+ },
+ "PE3": {
+ "ifacename": "eth2",
+ "ip": "10.10.10.1",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.1"
+ },
+ },
+ "PE2": {
+ "PE1": {
+ "ifacename": "eth1",
+ "ip": "10.10.10.10",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.2"
+ },
+ "PE3": {
+ "ifacename": "eth2",
+ "ip": "10.10.10.6",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.2"
+ }
+ },
+ "PE3": {
+ "PE1": {
+ "ifacename": "eth1",
+ "ip": "10.10.10.2",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.3"
+ },
+ "PE2": {
+ "ifacename": "eth2",
+ "ip": "10.10.10.5",
+ "mask": "30",
+ "internal_local_ip": "10.255.255.3"
+ }
+ }
+ }
+
+ @staticmethod
+ def get_pe_vnf_index(pe_name):
+ if pe_name not in OSM_MWC_Demo.VNF_INDEX_NAME_MAP:
+ raise ValueError("Could not find PE name: %s", pe_name)
+
+ return OSM_MWC_Demo.VNF_INDEX_NAME_MAP[pe_name]
+
+ @staticmethod
+ def get_src_core_iface(src_pe_name, dest_pe_name):
+ return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["ifacename"]
+
+ @staticmethod
+ def get_local_ip(src_pe_name, dest_pe_name):
+ return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["ip"]
+
+ @staticmethod
+ def get_remote_ip(src_pe_name, dest_pe_name):
+ return OSM_MWC_Demo.CORE_PE_CONN_MAP[dest_pe_name][src_pe_name]["ip"]
+
+ @staticmethod
+ def get_internal_local_ip(src_pe_name, dest_pe_name):
+ return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["internal_local_ip"]
+
+ @staticmethod
+ def get_internal_remote_ip(src_pe_name, dest_pe_name):
+ return OSM_MWC_Demo.CORE_PE_CONN_MAP[dest_pe_name][src_pe_name]["internal_local_ip"]
+
+
+def add_pe_corporation(src_pe_name, src_pe_charm, src_pe_group_cfg, corporation_name):
+ domain_name = corporation_name
+ vlan_id = src_pe_group_cfg.vlan_id
+ corp_gw = src_pe_group_cfg.corp_gateway
+ corp_net = src_pe_group_cfg.corp_network
+
+ user_iface = src_pe_group_cfg.interface_name
+
+ src_pe_charm.add_corporation(domain_name, user_iface, vlan_id, corp_gw, corp_net)
+
+
+def connect_pe_domains(src_pe_name, src_pe_charm, dest_pe_name, corporation_name, tunnel_key):
+ domain_name = corporation_name
+ core_iface_name = OSM_MWC_Demo.get_src_core_iface(src_pe_name, dest_pe_name)
+ local_ip = OSM_MWC_Demo.get_local_ip(src_pe_name, dest_pe_name)
+ remote_ip = OSM_MWC_Demo.get_remote_ip(src_pe_name, dest_pe_name)
+ internal_local_ip = OSM_MWC_Demo.get_internal_local_ip(src_pe_name, dest_pe_name)
+ internal_remote_ip = OSM_MWC_Demo.get_internal_remote_ip(src_pe_name, dest_pe_name)
+
+
+ src_pe_idx = OSM_MWC_Demo.get_pe_vnf_index(src_pe_name)
+ dest_pe_idx = OSM_MWC_Demo.get_pe_vnf_index(dest_pe_name)
+
+ # Create a 4 digit hash of the corporation name
+ hash_object = hashlib.md5(corporation_name.encode())
+ corp_hash = hash_object.hexdigest()[-4:]
+
+ # Tunnel name is the 4 digit corporation name hash followed by
+ # src index and dest index. When there are less than 10 PE's
+ # this creates a 8 character tunnel name which is the limit.
+ tunnel_name = "".join([corp_hash, "_", str(src_pe_idx), str(dest_pe_idx)])
+
+ src_pe_charm.connect_domains(domain_name, core_iface_name, local_ip, remote_ip,
+ internal_local_ip, internal_remote_ip, tunnel_name,
+ tunnel_key)
+
+
+def main(argv=sys.argv[1:]):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("yaml_cfg_file", type=argparse.FileType('r'))
+ parser.add_argument("--dry-run", action="store_true")
+ parser.add_argument("--quiet", "-q", dest="verbose", action="store_false")
+ args = parser.parse_args()
+ if args.verbose:
+ ch.setLevel(logging.DEBUG)
+
+ global dry_run
+ dry_run = args.dry_run
+
+ yaml_str = args.yaml_cfg_file.read()
+
+ juju_cfg = JujuClientConfig.from_yaml_cfg(yaml_str)
+ juju_client = JujuClient(juju_cfg.host, juju_cfg.port, juju_cfg.user, juju_cfg.secret)
+
+ juju_vnf_config = JujuVNFConfig.from_yaml_cfg(yaml_str)
+
+ rpc_request = AddCorporationRequest.from_yaml_cfg(yaml_str)
+ pe_param_group_map = rpc_request.get_pe_parameter_group_map()
+
+ pe_name_charm_map = {}
+ for pe_name, pe_group_cfg in pe_param_group_map.items():
+ # The PE name (i.e. PE1) must be in the parameter group name so we can correlate
+ # to an actual VNF in the descriptor.
+ pe_vnf_index = OSM_MWC_Demo.get_pe_vnf_index(pe_name)
+
+ # Get the deployed VNFR charm service name
+ pe_charm_service_name = juju_vnf_config.get_service_name(pe_vnf_index)
+
+ pe_name_charm_map[pe_name] = SixWindPEProxyCharm(juju_client, pe_charm_service_name)
+
+ # At this point we have SixWindPEProxyCharm() instances for each PE and each
+ # PE param group configuration.
+ for src_pe_name in pe_param_group_map:
+ add_pe_corporation(
+ src_pe_name=src_pe_name,
+ src_pe_charm=pe_name_charm_map[src_pe_name],
+ src_pe_group_cfg=PEGroupConfig(pe_param_group_map[src_pe_name]),
+ corporation_name=rpc_request.corporation_name
+ )
+
+ # Create a permutation of all PE's involved in this topology and connect
+ # them together by creating tunnels with matching keys
+ for src_pe_name, dest_pe_name in itertools.permutations(pe_name_charm_map, 2):
+ connect_pe_domains(
+ src_pe_name=src_pe_name,
+ src_pe_charm=pe_name_charm_map[src_pe_name],
+ dest_pe_name=dest_pe_name,
+ corporation_name=rpc_request.corporation_name,
+ tunnel_key=rpc_request.tunnel_key,
+ )
+
+if __name__ == "__main__":
+ try:
+ main()
+ except Exception as e:
+ logger.exception("Caught exception when executing add_corporation ns")
+ raise
--- /dev/null
+initial_config_primitive:
+- name: config
+ parameter:
+ hostname: pe1
+ pass: 6windos
+ user: root
+ vpe-router: <rw_mgmt_ip>
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.9/30
+ iface-name: eth1
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.1/30
+ iface-name: eth2
+- name: configure-interface
+ parameter:
+ iface-name: eth3
--- /dev/null
+initial_config_primitive:
+- name: config
+ parameter:
+ hostname: pe2
+ pass: 6windos
+ user: root
+ vpe-router: <rw_mgmt_ip>
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.10/30
+ iface-name: eth1
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.6/30
+ iface-name: eth2
+- name: configure-interface
+ parameter:
+ iface-name: eth3
--- /dev/null
+initial_config_primitive:
+- name: config
+ parameter:
+ hostname: pe3
+ pass: 6windos
+ user: root
+ vpe-router: <rw_mgmt_ip>
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.2/30
+ iface-name: eth1
+- name: configure-interface
+ parameter:
+ cidr: 10.10.10.5/30
+ iface-name: eth2
+- name: configure-interface
+ parameter:
+ iface-name: eth3
+- name: configure-interface
+ parameter:
+ iface-name: eth4
--- /dev/null
+vnfd:vnfd-catalog:
+ vnfd:
+ - id: 6WindTR1.1.2
+ name: 6WindTR1.1.2
+ short-name: 6WindTR1.1.2
+ logo: 6wind_2x.png
+ mgmt-interface:
+ vdu-id: VM
+ vnf-configuration:
+ config-attributes:
+ config-delay: '0'
+ config-priority: '0'
+ config-primitive:
+ - name: configure-interface
+ parameter:
+ - name: iface-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: cidr
+ data-type: STRING
+ - name: add-corporation
+ parameter:
+ - name: domain-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: iface-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: vlan-id
+ data-type: INTEGER
+ mandatory: 'true'
+ - name: cidr
+ data-type: STRING
+ mandatory: 'true'
+ - name: area
+ data-type: STRING
+ mandatory: 'true'
+ - name: subnet-cidr
+ data-type: STRING
+ mandatory: 'true'
+ - name: subnet-area
+ data-type: STRING
+ mandatory: 'true'
+ - name: delete-corporation
+ parameter:
+ - name: domain-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: cidr
+ data-type: STRING
+ mandatory: 'true'
+ - name: area
+ data-type: STRING
+ mandatory: 'true'
+ - name: subnet-cidr
+ data-type: STRING
+ mandatory: 'true'
+ - name: subnet-area
+ data-type: STRING
+ mandatory: 'true'
+ - name: connect-domains
+ parameter:
+ - name: domain-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: iface-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: tunnel-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: local-ip
+ data-type: STRING
+ mandatory: 'true'
+ - name: remote-ip
+ data-type: STRING
+ mandatory: 'true'
+ - name: tunnel-key
+ data-type: STRING
+ mandatory: 'true'
+ - name: internal-local-ip
+ data-type: STRING
+ mandatory: 'true'
+ - name: internal-remote-ip
+ data-type: STRING
+ mandatory: 'true'
+ - name: tunnel-type
+ data-type: STRING
+ mandatory: 'false'
+ default-value: 'gre'
+ - name: delete-domain-connection
+ parameter:
+ - name: domain-name
+ data-type: STRING
+ mandatory: 'true'
+ - name: tunnel-name
+ data-type: STRING
+ mandatory: 'true'
+ juju:
+ charm: vpe-router
+ connection-point:
+ - name: eth0
+ type: VPORT
+ - name: xe0
+ type: VPORT
+ - name: xe1
+ type: VPORT
+ - name: xe2
+ type: VPORT
+ - name: xe3
+ type: VPORT
+ vdu:
+ - id: VM
+ name: VM
+ image: /mnt/powervault/virtualization/vnfs/6wind/6wind-turbo-router-1.1.2.img.qcow2
+ mgmt-vpci: 0000:00:0a.0
+ vm-flavor:
+ memory-mb: '8192'
+ vcpu-count: '12'
+ external-interface:
+ - name: eth0
+ virtual-interface:
+ bandwidth: '1000000000'
+ type: OM-MGMT
+ vpci: '0000:00:03.0'
+ vnfd-connection-point-ref: eth0
+ - name: xe0
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:05.0'
+ vnfd-connection-point-ref: xe0
+ - name: xe1
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:06.0'
+ vnfd-connection-point-ref: xe1
+ - name: xe2
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:07.0'
+ vnfd-connection-point-ref: xe2
+ - name: xe3
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:08.0'
+ vnfd-connection-point-ref: xe3
+ guest-epa:
+ cpu-pinning-policy: DEDICATED
+ cpu-thread-pinning-policy: PREFER
+ mempage-size: LARGE
+ numa-node-policy:
+ mem-policy: STRICT
+ node:
+ - id: '0'
+ paired-threads:
+ num-paired-threads: '6'
+ paired-thread-ids:
+ - thread-a: '0'
+ thread-b: '1'
+ - thread-a: '2'
+ thread-b: '3'
+ - thread-a: '4'
+ thread-b: '5'
+ - thread-a: '6'
+ thread-b: '7'
+ - thread-a: '8'
+ thread-b: '9'
+ - thread-a: '10'
+ thread-b: '11'
+ node-cnt: '1'
+ host-epa:
+ om-cpu-feature:
+ - 64b
+ - iommu
+ - lps
+ - tlbps
+ - hwsv
+ - dioc
+ - ht
+ om-cpu-model-string: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz
+ hypervisor-epa:
+ type: REQUIRE_KVM
+ version: 10002|12001|2.6.32-358.el6.x86_64
--- /dev/null
+{
+ "layers": [
+ "layer:basic",
+ "vpe-router",
+ "build"
+ ],
+ "signatures": {
+ "hooks/stop": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "wheelhouse/ecdsa-0.13.tar.gz": [
+ "vpe-router",
+ "dynamic",
+ "64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa"
+ ],
+ "wheelhouse/charms.reactive-0.3.8.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "3f8722d85f7d489f8414d11fc2a3e8793c68000f7a1bc7b1ad71120e037aebee"
+ ],
+ "hooks/hook.template": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "wheelhouse/pycrypto-2.6.1.tar.gz": [
+ "vpe-router",
+ "dynamic",
+ "f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
+ ],
+ "hooks/start": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "wheelhouse/pip-7.1.2.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "ca047986f0528cfa975a14fb9f7f106271d4e0c3fe1ddced6c1db2e7ae57a477"
+ ],
+ "wheelhouse/PyYAML-3.11.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "c36c938a872e5ff494938b33b14aaa156cb439ec67548fcab3535bb78b0846e8"
+ ],
+ "Makefile": [
+ "layer:basic",
+ "static",
+ "f91213a68bc5edce9ebe0615b70cc908ea45466c2e205fb6cfe9c35d9c3fde4b"
+ ],
+ "reactive/__init__.py": [
+ "layer:basic",
+ "static",
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ ],
+ "wheelhouse/Jinja2-2.8.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "bc1ff2ff88dbfacefde4ddde471d1417d3b304e8df103a7a9437d47269201bf4"
+ ],
+ "hooks/upgrade-charm": [
+ "layer:basic",
+ "static",
+ "b78e405476402d34624c70822d9a60be2f4f85255765f619c0ecfe18f5f934ea"
+ ],
+ "actions/add-corporation": [
+ "vpe-router",
+ "static",
+ "951055318724d05aa82fa9757143561ecf3617a3bd2eaebb08533ed1ae897ade"
+ ],
+ ".build.manifest": [
+ "build",
+ "dynamic",
+ "unchecked"
+ ],
+ "wheelhouse/charmhelpers-0.6.1.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "c41a4cb3dcf6aa35e115addf9fb83a94585a4ff3bddc63148983431af45905f8"
+ ],
+ "actions/delete-corporation": [
+ "vpe-router",
+ "static",
+ "1e380d728790fa946e2429eaed31ff11aa4186cc287b818d8a91da7da291a6b3"
+ ],
+ "hooks/update-status": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "hooks/leader-settings-changed": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "wheelhouse/Tempita-0.5.2.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "cacecf0baa674d356641f1d406b8bff1d756d739c46b869a54de515d08e6fc9c"
+ ],
+ "actions.yaml": [
+ "vpe-router",
+ "static",
+ "619cedd685181c02ae36d04014bc4763c1e4e1b0a1b8a743d1a239c442f2883b"
+ ],
+ "README.md": [
+ "layer:basic",
+ "static",
+ "5d5101eb0f2eb90eb0959438416ceb5e9b82c7746a385eb64ccb8a8ffe01e92b"
+ ],
+ "reactive/vpe_router.py": [
+ "vpe-router",
+ "static",
+ "479874bbe5db71ddc0b3e4e0adab051540dbc8d30021800c42a1b058bf9dcd94"
+ ],
+ "tox.ini": [
+ "layer:basic",
+ "static",
+ "5efb9280763f1f4cb861485e80863caafc9cd5ab1176543e911c27519436de7a"
+ ],
+ "metadata.yaml": [
+ "vpe-router",
+ "dynamic",
+ "a7bf974efb4a29810de06626025fbc3b158053c70b0d67eb4b142a2ac087c5c0"
+ ],
+ "wheelhouse/pyaml-15.8.2.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "9c54fb5f17b58572c4cef50affea60bb73f445ab153580dac07a12383712b5b8"
+ ],
+ "copyright": [
+ "layer:basic",
+ "static",
+ "1e2afbd75c71affa132ae7ee3327cb29b5e4b9d9705f27dfd03857c326f50c5c"
+ ],
+ "requirements.txt": [
+ "layer:basic",
+ "static",
+ "0f1c70d27e26005a96d66ad54482877ae20f7737693c833e29dd72bd6ac24892"
+ ],
+ "wheelhouse/netaddr-0.7.18.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "a1f5c9fcf75ac2579b9995c843dade33009543c04f218ff7c007b3c81695bd19"
+ ],
+ "wheelhouse/paramiko-1.16.0.tar.gz": [
+ "vpe-router",
+ "dynamic",
+ "3297ebd3cd072f573772f7c7426939a443c62c458d54bb632ff30fd6ecf96892"
+ ],
+ "actions/connect-domains": [
+ "vpe-router",
+ "static",
+ "cdc11dd947a97b1e6ecb92b95e5e2b2676a5b1a366638a80b5fcf052b6fe240d"
+ ],
+ ".gitignore": [
+ "layer:basic",
+ "static",
+ "0da5c4dcda27cd6406e5bb81cbf68ddccaf728ac764ec15053a165c1449d87d9"
+ ],
+ "lib/charms/router.py": [
+ "vpe-router",
+ "static",
+ "b29712ab37799310107c99bb79ce90a991c5ebf95d513bad127b3fabd02df4a7"
+ ],
+ "hooks/install": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "wheelhouse/MarkupSafe-0.23.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "a4ec1aff59b95a14b45eb2e23761a0179e98319da5a7eb76b56ea8cdc7b871c3"
+ ],
+ "layer.yaml": [
+ "vpe-router",
+ "dynamic",
+ "b6f7fe3a054fa4c8a17d4fa922ee2b0624f75cb39fdcc59c511cb55455425f8b"
+ ],
+ "config.yaml": [
+ "vpe-router",
+ "dynamic",
+ "989e451c1dc464082f3e1122bd784362502af58680ad8e77b88ce00db0ec2246"
+ ],
+ "hooks/leader-elected": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ],
+ "lib/charms/bootstrap.py": [
+ "layer:basic",
+ "static",
+ "bec7997003dbe44e9bbe85f0df598746c868fe72d1971a99d357bf3512453c70"
+ ],
+ "lib/charms/layer.py": [
+ "layer:basic",
+ "static",
+ "3accb93272464875583f9b661dc024b4adc67617354bc21d8a7f74284ae4deb4"
+ ],
+ "actions/delete-domain-connection": [
+ "vpe-router",
+ "static",
+ "0b59e146b4b0223f5593cd4bad9a829822713b10b2ccab46d07a531eb9e20216"
+ ],
+ "wheelhouse/six-1.10.0.tar.gz": [
+ "layer:basic",
+ "dynamic",
+ "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a"
+ ],
+ "hooks/config-changed": [
+ "layer:basic",
+ "static",
+ "21759be2af2e65c9e29531b293fd77fc1c710468ece35bc1cb4360cdefd997b0"
+ ]
+ }
+}
\ No newline at end of file
--- /dev/null
+*.pyc
+*~
+.ropeproject
+.settings
+.tox
--- /dev/null
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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 a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
--- /dev/null
+#!/usr/bin/make
+PYTHON := /usr/bin/env python
+
+all: lint test build
+
+
+build: unit_test
+ juju-compose -o ~/charms .
+
+lint:
+ @flake8 --exclude hooks/charmhelpers hooks unit_tests tests
+ @charm proof
+
+unit_test:
+ @echo Starting tests...
+ tox
+
+test:
+ @echo Starting Amulet tests...
+ # coreycb note: The -v should only be temporary until Amulet sends
+ # raise_status() messages to stderr:
+ # https://bugs.launchpad.net/amulet/+bug/1320357
+ @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
--- /dev/null
+# Overview
+
+This is the base layer for all charms [built using layers][building]. It
+provides all of the standard Juju hooks and runs the
+[charms.reactive.main][charms.reactive] loop for them. It also bootstraps the
+[charm-helpers][] and [charms.reactive][] libraries and all of their
+dependencies for use by the charm.
+
+# Usage
+
+To create a charm layer using this base layer, you need only include it in
+a `layer.yaml` file:
+
+```yaml
+includes: ['layer:basic']
+```
+
+This will fetch this layer from [interfaces.juju.solutions][] and incorporate
+it into your charm layer. You can then add handlers under the `reactive/`
+directory. Note that **any** file under `reactive/` will be expected to
+contain handlers, whether as Python decorated functions or [executables][non-python]
+using the [external handler protocol][].
+
+You can also define Python libraries under `lib/charms/X` where `X` is a
+package under the `charms.` namespace for your charm. See [PyPI][pypi charms.X]
+for what packages already exist under the `charms.` namespace.
+
+# Hooks
+
+This layer provides hooks that other layers can react to using the decorators
+of the [charms.reactive][] library:
+
+ * `config-changed`
+ * `install`
+ * `leader-elected`
+ * `leader-settings-changed`
+ * `start`
+ * `stop`
+ * `upgrade-charm`
+ * `update-status`
+
+Other hooks are not implemented at this time. A new layer can implement storage
+or relation hooks in their own layer by putting them in the `hooks` directory.
+
+**Note:** Because `update-status` is invoked every 5 minutes, you should take
+care to ensure that your reactive handlers only invoke expensive operations
+when absolutely necessary. It is recommended that you use helpers like
+[`@only_once`][], [`@when_file_changed`][], and [`data_changed`][] to ensure
+that handlers run only when necessary.
+
+# Layer Configuration
+
+This layer does not currently support any configuration.
+
+
+# Reactive States
+
+This layer currently does not set any reactive states.
+
+
+# Actions
+
+This layer currently does not define any actions.
+
+
+[building]: https://jujucharms.com/docs/devel/authors-charm-building
+[charm-helpers]: https://pythonhosted.org/charmhelpers/
+[charms.reactive]: https://pythonhosted.org/charms.reactive/
+[interfaces.juju.solutions]: http://interfaces.juju.solutions/
+[non-python]: https://pythonhosted.org/charms.reactive/#non-python-reactive-handlers
+[external handler protocol]: https://pythonhosted.org/charms.reactive/charms.reactive.bus.html#charms.reactive.bus.ExternalHandler
+[pypi charms.X]: https://pypi.python.org/pypi?%3Aaction=search&term=charms.&submit=search
+[`@only_once`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.only_once
+[`@when_file_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.when_file_changed
+[`data_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.helpers.html#charms.reactive.helpers.data_changed
--- /dev/null
+configure-interface:
+ description: Configure an ethernet interface.
+ params:
+ iface-name:
+ type: string
+ description: Device name, e.g. eth1
+ cidr:
+ type: string
+ description: Network range to assign to the interface
+ required: [iface-name]
+add-corporation:
+ description: Add a new corporation to the router
+ params:
+ domain-name:
+ type: string
+ description: Name of the vlan corporation
+ iface-name:
+ type: string
+ description: Device name. eg eth1
+ vlan-id:
+ type: integer
+ description: The name of the vlan?
+ cidr:
+ type: string
+ description: Network range to assign to the tagged vlan-id
+ area:
+ type: string
+ description: Link State Advertisements (LSA) type
+ subnet-cidr:
+ type: string
+ description: Network range
+ subnet-area:
+ type: string
+ description: Link State Advertisements (LSA) type
+ required: [domain-name, iface-name, vlan-id, cidr, area, subnet-cidr, subnet-area]
+delete-corporation:
+ description: Remove the corporation from the router completely
+ params:
+ domain-name:
+ type: string
+ description: The domain of the corporation to remove
+ cidr:
+ type: string
+ description: Network range to assign to the tagged vlan-id
+ area:
+ type: string
+ description: Link State Advertisements (LSA) type
+ subnet-cidr:
+ type: string
+ description: Network range
+ subnet-area:
+ type: string
+ description: Link State Advertisements (LSA) type
+ required: [domain-name, cidr, area, subnet-cidr, subnet-area]
+connect-domains:
+ description: Connect the router to another router, where the same domain is present
+ params:
+ domain-name:
+ type: string
+ description: The domain of the coproration to connect
+ iface-name:
+ type: string
+ description: Device name. eg eth1
+ tunnel-name:
+ type: string
+ description: Name of the tunnel ?
+ local-ip:
+ type: string
+ description: local ip ?
+ remote-ip:
+ type: string
+ description: remote ip ?
+ tunnel-key:
+ type: string
+ description: tunnel key?
+ internal-local-ip:
+ type: string
+ description: internal local ip?
+ internal-remote-ip:
+ type: string
+ description: internal remote ip?
+ tunnel-type:
+ type: string
+ default: gre
+ description: The type of tunnel to establish.
+ required: [domain-name, iface-name, tunnel-name, local-ip, remote-ip, tunnel-key, internal-local-ip, internal-remote-ip]
+delete-domain-connection:
+ description: Remove the tunnel to another router where the domain is present.
+ params:
+ domain-name:
+ type: string
+ description: The domain of the corporation to unlink
+ tunnel-name:
+ type: string
+ description: The name of the tunnel to unlink that the domain-name is attached to
+ required: [domain-name, tunnel-name]
--- /dev/null
+#!/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
+
+"""
+`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('vpe.add-corporation')
+
+try:
+ main()
+except Exception as e:
+ action_fail(repr(e))
--- /dev/null
+#!/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
+
+"""
+`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('vpe.configure-interface')
+
+try:
+ main()
+except Exception as e:
+ action_fail(repr(e))
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`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('vpe.connect-domains')
+
+try:
+ main()
+except Exception as e:
+ action_fail(repr(e))
--- /dev/null
+#!/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
+
+
+"""
+`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('vpe.delete-corporation')
+
+try:
+ main()
+except Exception as e:
+ action_fail(repr(e))
--- /dev/null
+#!/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
+
+
+"""
+`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('vpe.delete-domain-connection')
+
+try:
+ main()
+except Exception as e:
+ action_fail(repr(e))
--- /dev/null
+options:
+ vpe-router:
+ default:
+ type: string
+ description: Hostname or IP of the vpe router to connect to
+ user:
+ type: string
+ default: root
+ description: Username for VPE Router
+ pass:
+ type: string
+ default:
+ description: Password for VPE Router
+ hostname:
+ type: string
+ default:
+ description: The hostname to set the vpe router to.
--- /dev/null
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
+
+Files: *
+Copyright: 2015, Canonical Ltd.
+License: GPL-3
+
+License: GPL-3
+ On Debian GNU/Linux system you can find the complete text of the
+ GPL-3 license in '/usr/share/common-licenses/GPL-3'
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import sys
+sys.path.append('lib')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+#!/usr/bin/env python3
+
+# Load modules from $CHARM_DIR/lib
+import os
+import sys
+sys.path.append('lib')
+
+# This is an upgrade-charm context, make sure we install latest deps
+if not os.path.exists('wheelhouse/.upgrade'):
+ open('wheelhouse/.upgrade', 'w').close()
+ if os.path.exists('wheelhouse/.bootstrapped'):
+ os.unlink('wheelhouse/.bootstrapped')
+else:
+ os.unlink('wheelhouse/.upgrade')
+
+from charms.bootstrap import bootstrap_charm_deps
+bootstrap_charm_deps()
+
+
+# This will load and run the appropriate @hook and other decorated
+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
+# and $CHARM_DIR/hooks/relations.
+#
+# See https://jujucharms.com/docs/stable/authors-charm-building
+# for more information on this pattern.
+from charms.reactive import main
+main()
--- /dev/null
+includes:
+- layer:basic
+is: vpe-router
--- /dev/null
+import os
+import sys
+import shutil
+from glob import glob
+from subprocess import check_call
+
+
+def bootstrap_charm_deps():
+ """
+ Set up the base charm dependencies so that the reactive system can run.
+ """
+ venv = os.path.abspath('../.venv')
+ vbin = os.path.join(venv, 'bin')
+ vpip = os.path.join(vbin, 'pip')
+ vpy = os.path.join(vbin, 'python')
+ if os.path.exists('wheelhouse/.bootstrapped'):
+ from charms import layer
+ cfg = layer.options('basic')
+ if cfg.get('use_venv') and '.venv' not in sys.executable:
+ # activate the venv
+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
+ reload_interpreter(vpy)
+ return
+ # bootstrap wheelhouse
+ if os.path.exists('wheelhouse'):
+ apt_install(['python3-pip', 'python3-yaml'])
+ from charms import layer
+ cfg = layer.options('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'):
+ apt_install(['python-virtualenv'])
+ cmd = ['virtualenv', '--python=python3', 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'])
+ # install the rest of the wheelhouse deps
+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] + glob('wheelhouse/*'))
+ 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')
+ # 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 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.execle(python, python, sys.argv[0], 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']
+ check_call(cmd + packages, env=env)
--- /dev/null
+
+import os
+import yaml
+
+
+class LayerOptions(dict):
+ def __init__(self, layer_file, section=None):
+ with open(layer_file) as f:
+ layer = yaml.safe_load(f.read())
+ opts = layer.get('options', {})
+ if section and section in opts:
+ super(LayerOptions, self).__init__(opts.get(section))
+ else:
+ super(LayerOptions, self).__init__(opts)
+
+
+def options(section=None, layer_file=None):
+ if not layer_file:
+ base_dir = os.environ.get('CHARM_DIR', os.getcwd())
+ layer_file = os.path.join(base_dir, 'layer.yaml')
+
+ return LayerOptions(layer_file, section)
--- /dev/null
+
+import paramiko
+import subprocess
+
+from charmhelpers.core.hookenv import config
+
+
+class NetNS(object):
+ def __init__(self, name):
+ pass
+
+ @classmethod
+ def create(cls, name):
+ # @TODO: Need to check if namespace exists already
+ try:
+ ip('netns', 'add', name)
+ except Exception as e:
+ raise Exception('could not create net namespace: %s' % e)
+
+ return cls(name)
+
+ def up(self, iface, cidr):
+ self.do('ip', 'link', 'set', 'dev', iface, 'up')
+ self.do('ip', 'address', 'add', cidr, 'dev', iface)
+
+ def add_iface(self, iface):
+ ip('link', 'set', 'dev', iface, 'netns', self.name)
+
+ def do(self, *cmd):
+ ip(*['netns', 'exec', self.name] + cmd)
+
+
+def ip(*args):
+ return _run(['ip'] + list(args))
+
+
+def _run(cmd, env=None):
+ if isinstance(cmd, str):
+ cmd = cmd.split() if ' ' in cmd else [cmd]
+
+ cfg = config()
+ if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
+ router = cfg['vpe-router']
+ user = cfg['user']
+ passwd = cfg['pass']
+
+ if router and user and passwd:
+ return ssh(cmd, router, user, passwd)
+
+ p = subprocess.Popen(cmd,
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ retcode = p.poll()
+ if retcode > 0:
+ raise subprocess.CalledProcessError(returncode=retcode,
+ cmd=cmd,
+ output=stderr.decode("utf-8").strip())
+ return (''.join(stdout), ''.join(stderr))
+
+
+def ssh(cmd, host, user, password=None):
+ ''' Suddenly this project needs to SSH to something. So we replicate what
+ _run was doing with subprocess using the Paramiko library. This is
+ temporary until this charm /is/ the VPE Router '''
+
+ cmds = ' '.join(cmd)
+ client = paramiko.SSHClient()
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ client.connect(host, port=22, username=user, password=password)
+
+ stdin, stdout, stderr = client.exec_command(cmds)
+ retcode = stdout.channel.recv_exit_status()
+ client.close() # @TODO re-use connections
+ if retcode > 0:
+ output = stderr.read().strip()
+ raise subprocess.CalledProcessError(returncode=retcode, cmd=cmd,
+ output=output)
+ return (''.join(stdout), ''.join(stderr))
--- /dev/null
+name: vpe-router
+summary: setup a virtualized PE Router with GRE tunnels
+description: |
+ this charm, when deployed and configured, will provide a secure virtualized
+ provider edge router.
+peers:
+ loadbalance:
+ interface: vpe-router
+maintainers:
+- Marco Ceppi <marco.ceppi@canonical.com>
+- Adam Israel <adam.israel@canonical.com>
--- /dev/null
+
+from charmhelpers.core.hookenv import (
+ config,
+ status_set,
+ action_get,
+ action_fail,
+ log,
+)
+
+from charms.reactive import (
+ hook,
+ when,
+ when_not,
+ helpers,
+ set_state,
+ remove_state,
+)
+
+from charms import router
+import subprocess
+
+cfg = config()
+
+
+@hook('config-changed')
+def validate_config():
+ try:
+ """
+ If the ssh credentials are available, we'll act as a proxy charm.
+ Otherwise, we execute against the unit we're deployed on to.
+ """
+ if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
+ routerip = cfg['vpe-router']
+ user = cfg['user']
+ passwd = cfg['pass']
+
+ if routerip and user and passwd:
+ # Assumption: this will be a root user
+ out, err = router.ssh(['whoami'], routerip,
+ user, passwd)
+ if out.strip() != user:
+ raise Exception('invalid credentials')
+
+ # Set the router's hostname
+ try:
+ if user == 'root' and 'hostname' in cfg:
+ hostname = cfg['hostname']
+ out, err = router.ssh(['hostname', hostname],
+ routerip,
+ user, passwd)
+ out, err = router.ssh(['sed',
+ '-i',
+ '"s/hostname.*$/hostname %s/"'
+ % hostname,
+ '/usr/admin/global/hostname.sh'
+ ],
+ routerip,
+ user, passwd)
+
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ raise
+
+ set_state('vpe.configured')
+ status_set('active', 'ready!')
+
+ except Exception as e:
+ log(repr(e))
+ remove_state('vpe.configured')
+ status_set('blocked', 'validation failed: %s' % e)
+
+
+@when_not('vpe.configured')
+def not_ready_add():
+ actions = [
+ 'vpe.add-corporation',
+ 'vpe.connect-domains',
+ 'vpe.delete-domain-connections',
+ 'vpe.remove-corporation',
+ 'vpe.configure-interface',
+ 'vpe.configure-ospf',
+ ]
+
+ if helpers.any_states(*actions):
+ action_fail('VPE is not configured')
+
+ status_set('blocked', 'vpe is not configured')
+
+
+def start_ospfd():
+ # We may want to make this configurable via config setting
+ ospfd = '/usr/local/bin/ospfd'
+
+ try:
+ (stdout, stderr) = router._run(['touch',
+ '/usr/admin/global/ospfd.conf'])
+ (stdout, stderr) = router._run([ospfd, '-d', '-f',
+ '/usr/admin/global/ospfd.conf'])
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+
+
+def configure_ospf(domain, cidr, area, subnet_cidr, subnet_area, enable=True):
+ """Configure the OSPF service"""
+
+ # Check to see if the OSPF daemon is running, and start it if not
+ try:
+ (stdout, stderr) = router._run(['pgrep', 'ospfd'])
+ except subprocess.CalledProcessError as e:
+ # If pgrep fails, the process wasn't found.
+ start_ospfd()
+ log('Command failed (ospfd not running): %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+
+ upordown = ''
+ if not enable:
+ upordown = 'no'
+ try:
+ vrfctl = '/usr/local/bin/vrfctl'
+ vtysh = '/usr/local/bin/vtysh'
+
+ (stdout, stderr) = router._run([vrfctl, 'list'])
+
+ domain_id = 0
+ for line in stdout.split('\n'):
+ if domain in line:
+ domain_id = int(line[3:5])
+
+ if domain_id > 0:
+ router._run([vtysh,
+ '-c',
+ '"configure terminal"',
+ '-c',
+ '"router ospf %d vr %d"' % (domain_id, domain_id),
+ '-c',
+ '"%s network %s area %s"' % (upordown, cidr, area),
+ '-c',
+ '"%s network %s area %s"' % (upordown,
+ subnet_cidr,
+ subnet_area),
+ ])
+
+ else:
+ log("Invalid domain id")
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ finally:
+ remove_state('vpe.configure-interface')
+ status_set('active', 'ready!')
+
+
+@when('vpe.configured')
+@when('vpe.configure-interface')
+def configure_interface():
+ """
+ Configure an ethernet interface
+ """
+ iface_name = action_get('iface-name')
+ cidr = action_get('cidr')
+
+ # cidr is optional
+ if cidr:
+ try:
+ # Add may fail, but change seems to add or update
+ router.ip('address', 'change', cidr, 'dev', iface_name)
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ return
+ finally:
+ remove_state('vpe.configure-interface')
+ status_set('active', 'ready!')
+
+ try:
+ router.ip('link', 'set', 'dev', iface_name, 'up')
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ finally:
+ remove_state('vpe.configure-interface')
+ status_set('active', 'ready!')
+
+
+@when('vpe.configured')
+@when('vpe.add-corporation')
+def add_corporation():
+ '''
+ Create and Activate the network corporation
+ '''
+ domain_name = action_get('domain-name')
+ iface_name = action_get('iface-name')
+ # HACK: python's list, used deeper, throws an exception on ints in a tuple
+ vlan_id = str(action_get('vlan-id'))
+ cidr = action_get('cidr')
+ area = action_get('area')
+ subnet_cidr = action_get('subnet-cidr')
+ subnet_area = action_get('subnet-area')
+
+ iface_vlanid = '%s.%s' % (iface_name, vlan_id)
+
+ status_set('maintenance', 'adding corporation {}'.format(domain_name))
+
+ """
+ Attempt to run all commands to add the network corporation. If any step
+ fails, abort and call `delete_corporation()` to undo.
+ """
+ try:
+ """
+ $ ip link add link eth3 name eth3.103 type vlan id 103
+ """
+ router.ip('link',
+ 'add',
+ 'link',
+ iface_name,
+ 'name',
+ iface_vlanid,
+ 'type',
+ 'vlan',
+ 'id',
+ vlan_id)
+
+ """
+ $ ip netns add domain
+ """
+ router.ip('netns',
+ 'add',
+ domain_name)
+
+ """
+ $ ip link set dev eth3.103 netns corpB
+ """
+ router.ip('link',
+ 'set',
+ 'dev',
+ iface_vlanid,
+ 'netns',
+ domain_name)
+
+ """
+ $ ifconfig eth3 up
+ """
+ router._run(['ifconfig', iface_name, 'up'])
+
+ """
+ $ ip netns exec corpB ip link set dev eth3.103 up
+ """
+ router.ip('netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'link',
+ 'set',
+ 'dev',
+ iface_vlanid,
+ 'up')
+
+ """
+ $ ip netns exec corpB ip address add 10.0.1.1/24 dev eth3.103
+ """
+ mask = cidr.split("/")[1]
+ ip = '%s/%s' % (area, mask)
+ router.ip('netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'address',
+ 'add',
+ ip,
+ 'dev',
+ iface_vlanid)
+
+ configure_ospf(domain_name, cidr, area, subnet_cidr, subnet_area, True)
+
+ except subprocess.CalledProcessError as e:
+ delete_corporation()
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ finally:
+ remove_state('vpe.add-corporation')
+ status_set('active', 'ready!')
+
+
+@when('vpe.configured')
+@when('vpe.delete-corporation')
+def delete_corporation():
+
+ domain_name = action_get('domain-name')
+ cidr = action_get('cidr')
+ area = action_get('area')
+ subnet_cidr = action_get('subnet-cidr')
+ subnet_area = action_get('subnet-area')
+
+ status_set('maintenance', 'deleting corporation {}'.format(domain_name))
+
+ try:
+ """
+ Remove all tunnels defined for this domain
+
+ $ ip netns exec domain_name ip tun show
+ | grep gre
+ | grep -v "remote any"
+ | cut -d":" -f1
+ """
+ p = router.ip(
+ 'netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'tun',
+ 'show',
+ '|',
+ 'grep',
+ 'gre',
+ '|',
+ 'grep',
+ '-v',
+ '"remote any"',
+ '|',
+ 'cut -d":" -f1'
+ )
+
+ # `p` should be a tuple of (stdout, stderr)
+ tunnels = p[0].split('\n')
+
+ for tunnel in tunnels:
+ try:
+ """
+ $ ip netns exec domain_name ip link set $tunnel_name down
+ """
+ router.ip(
+ 'netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'link',
+ 'set',
+ tunnel,
+ 'down'
+ )
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ pass
+
+ try:
+ """
+ $ ip netns exec domain_name ip tunnel del $tunnel_name
+ """
+ router.ip(
+ 'netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'tunnel',
+ 'del',
+ tunnel
+ )
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ pass
+
+ """
+ Remove all interfaces associated to the domain
+
+ $ ip netns exec domain_name ifconfig | grep mtu | cut -d":" -f1
+ """
+ p = router.ip(
+ 'netns',
+ 'exec',
+ domain_name,
+ 'ifconfig',
+ '|',
+ 'grep mtu',
+ '|',
+ 'cut -d":" -f1'
+ )
+
+ ifaces = p[0].split('\n')
+ for iface in ifaces:
+
+ try:
+ """
+ $ ip netns exec domain_name ip link set $iface down
+ """
+ router.ip(
+ 'netns',
+ 'exec',
+ domain_name,
+ 'ip',
+ 'link',
+ 'set',
+ iface,
+ 'down'
+ )
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+
+ try:
+ """
+ $ ifconfig eth3 down
+ """
+ router._run(['ifconfig', iface, 'down'])
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ pass
+
+ try:
+ """
+ $ ip link del dev $iface
+ """
+ router.ip(
+ 'link',
+ 'del',
+ 'dev',
+ iface
+ )
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ pass
+
+ try:
+ """
+ Remove the domain
+
+ $ ip netns del domain_name
+ """
+ router.ip(
+ 'netns',
+ 'del',
+ domain_name
+ )
+ except subprocess.CalledProcessError as e:
+ log('Command failed: %s (%s)' % (' '.join(e.cmd), str(e.output)))
+ pass
+
+ try:
+ configure_ospf(domain_name,
+ cidr,
+ area,
+ subnet_cidr,
+ subnet_area,
+ False)
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+
+ except:
+ # Do nothing
+ log('delete-corporation failed.')
+ pass
+
+ finally:
+ remove_state('vpe.delete-corporation')
+ status_set('active', 'ready!')
+
+
+@when('vpe.configured')
+@when('vpe.connect-domains')
+def connect_domains():
+
+ params = [
+ 'domain-name',
+ 'iface-name',
+ 'tunnel-name',
+ 'local-ip',
+ 'remote-ip',
+ 'tunnel-key',
+ 'internal-local-ip',
+ 'internal-remote-ip',
+ 'tunnel-type',
+ ]
+
+ config = {}
+ for p in params:
+ config[p] = action_get(p)
+
+ status_set('maintenance', 'connecting domains')
+
+ try:
+ """
+ $ ip tunnel add tunnel_name mode gre local local_ip remote remote_ip
+ dev iface_name key tunnel_key csum
+ """
+ router.ip(
+ 'tunnel',
+ 'add',
+ config['tunnel-name'],
+ 'mode',
+ config['tunnel-type'],
+ 'local',
+ config['local-ip'],
+ 'remote',
+ config['remote-ip'],
+ 'dev',
+ config['iface-name'],
+ 'key',
+ config['tunnel-key'],
+ 'csum'
+ )
+
+ except subprocess.CalledProcessError as e:
+ log('Command failed (retrying with ip tunnel change): %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ try:
+ """
+ If the tunnel already exists (like gre0) and can't be deleted,
+ modify it instead of trying to add it.
+ """
+ router.ip(
+ 'tunnel',
+ 'change',
+ config['tunnel-name'],
+ 'mode',
+ config['tunnel-type'],
+ 'local',
+ config['local-ip'],
+ 'remote',
+ config['remote-ip'],
+ 'dev',
+ config['iface-name'],
+ 'key',
+ config['tunnel-key'],
+ 'csum'
+ )
+ except subprocess.CalledProcessError as e:
+ delete_domain_connection()
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ finally:
+ remove_state('vpe.connect-domains')
+ status_set('active', 'ready!')
+
+ try:
+ """
+ $ ip link set dev tunnel_name netns domain_name
+ """
+ router.ip(
+ 'link',
+ 'set',
+ 'dev',
+ config['tunnel-name'],
+ 'netns',
+ config['domain-name']
+ )
+
+ """
+ $ ip netns exec domain_name ip link set dev tunnel_name up
+ """
+ router.ip(
+ 'netns',
+ 'exec',
+ config['domain-name'],
+ 'ip',
+ 'link',
+ 'set',
+ 'dev',
+ config['tunnel-name'],
+ 'up'
+ )
+
+ """
+ $ ip netns exec domain_name ip address add internal_local_ip peer
+ internal_remote_ip dev tunnel_name
+ """
+ router.ip(
+ 'netns',
+ 'exec',
+ config['domain-name'],
+ 'ip',
+ 'address',
+ 'add',
+ config['internal-local-ip'],
+ 'peer',
+ config['internal-remote-ip'],
+ 'dev',
+ config['tunnel-name']
+ )
+ except subprocess.CalledProcessError as e:
+ delete_domain_connection()
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ finally:
+ remove_state('vpe.connect-domains')
+ status_set('active', 'ready!')
+
+
+@when('vpe.configured')
+@when('vpe.delete-domain-connection')
+def delete_domain_connection():
+ ''' Remove the tunnel to another router where the domain is present '''
+ domain = action_get('domain-name')
+ tunnel_name = action_get('tunnel-name')
+
+ status_set('maintenance', 'deleting domain connection: {}'.format(domain))
+
+ try:
+
+ try:
+ """
+ $ ip netns exec domain_name ip link set tunnel_name down
+ """
+ router.ip('netns',
+ 'exec',
+ domain,
+ 'ip',
+ 'link',
+ 'set',
+ tunnel_name,
+ 'down')
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+
+ try:
+ """
+ $ ip netns exec domain_name ip tunnel del tunnel_name
+ """
+ router.ip('netns',
+ 'exec',
+ domain,
+ 'ip',
+ 'tunnel',
+ 'del',
+ tunnel_name)
+ except subprocess.CalledProcessError as e:
+ action_fail('Command failed: %s (%s)' %
+ (' '.join(e.cmd), str(e.output)))
+ except:
+ pass
+ finally:
+ remove_state('vpe.delete-domain-connection')
+ status_set('active', 'ready!')
--- /dev/null
+flake8
+pytest
--- /dev/null
+[tox]
+skipsdist=True
+envlist = py34
+
+[testenv]
+commands = py.test -v
+deps =
+ -r{toxinidir}/requirements.txt
--- /dev/null
+vnfd:vnfd-catalog:
+ vnfd:
+ - id: gw_corpA_PE1
+ name: gw_corpA_PE1
+ short-name: gw_corpA_PE1
+ description: gw_corpA_PE1
+ connection-point:
+ - name: eth0
+ type: VPORT
+ - name: eth1
+ type: VPORT
+ - name: xe0
+ type: VPORT
+ mgmt-interface:
+ vdu-id: gw_corpA_PE1-VM
+ vdu:
+ - id: gw_corpA_PE1-VM
+ name: gw_corpA_PE1-VM
+ description: gw_corpA_PE1-VM
+ image: /mnt/powervault/virtualization/vnfs/demos/mwc2016/gw_corpA_PE1.qcow2
+ mgmt-vpci: 0000:00:0a.0
+ vm-flavor:
+ vcpu-count: '2'
+ memory-mb: '4096'
+ storage-gb: '10'
+ external-interface:
+ - name: eth0
+ virtual-interface:
+ bandwidth: '0'
+ type: VIRTIO
+ vpci: 0000:00:0a.0
+ vnfd-connection-point-ref: eth0
+ - name: eth1
+ virtual-interface:
+ bandwidth: '0'
+ type: OM-MGMT
+ vpci: 0000:00:0b.0
+ vnfd-connection-point-ref: eth1
+ - name: xe0
+ virtual-interface:
+ bandwidth: '10000000000'
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:10.0'
+ vnfd-connection-point-ref: xe0
+ guest-epa:
+ cpu-pinning-policy: DEDICATED
+ cpu-thread-pinning-policy: PREFER
+ mempage-size: LARGE
+ numa-node-policy:
+ mem-policy: STRICT
+ node:
+ - id: '0'
+ paired-threads:
+ num-paired-threads: '1'
+ node-cnt: '1'
--- /dev/null
+vnfd:vnfd-catalog:
+ vnfd:
+ - id: gw_corpA_PE2
+ name: gw_corpA_PE2
+ short-name: gw_corpA_PE2
+ description: gw_corpA_PE2
+ mgmt-interface:
+ vdu-id: gw_corpA_PE2-VM
+ connection-point:
+ - name: eth0
+ type: VPORT
+ - name: eth1
+ type: VPORT
+ - name: xe0
+ type: VPORT
+ vdu:
+ - id: gw_corpA_PE2-VM
+ name: gw_corpA_PE2-VM
+ description: gw_corpA_PE2-VM
+ image: /mnt/powervault/virtualization/vnfs/demos/mwc2016/gw_corpA_PE2.qcow2
+ mgmt-vpci: 0000:00:0a.0
+ vm-flavor:
+ memory-mb: '4096'
+ storage-gb: '10'
+ vcpu-count: '2'
+ external-interface:
+ - name: eth0
+ virtual-interface:
+ bandwidth: '0'
+ type: VIRTIO
+ vpci: 0000:00:0a.0
+ vnfd-connection-point-ref: eth0
+ - name: eth1
+ virtual-interface:
+ bandwidth: '0'
+ type: OM-MGMT
+ vpci: 0000:00:0b.0
+ vnfd-connection-point-ref: eth1
+ - name: xe0
+ virtual-interface:
+ bandwidth: '10000000000'
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:10.0'
+ vnfd-connection-point-ref: xe0
+ guest-epa:
+ cpu-pinning-policy: DEDICATED
+ cpu-thread-pinning-policy: PREFER
+ mempage-size: LARGE
+ numa-node-policy:
+ mem-policy: STRICT
+ node:
+ - id: '0'
+ paired-threads:
+ num-paired-threads: '1'
+ node-cnt: '1'
--- /dev/null
+vnfd:vnfd-catalog:
+ vnfd:
+ - id: IMS-ALLIN1_2p
+ name: IMS-ALLIN1_2p
+ short-name: IMS-ALLIN1_2p
+ description: IMS-ALLIN1_2p
+ logo: metaswitch_2x.png
+ mgmt-interface:
+ vdu-id: IMS-ALLIN1_2p-VM
+ vnf-configuration:
+ config-attributes:
+ config-delay: '0'
+ config-priority: '1'
+ config-primitive:
+ - name: config
+ parameter:
+ - name: home_domain
+ data-type: STRING
+ mandatory: 'true'
+ default-value: ims.com
+ - name: password
+ data-type: string
+ mandatory: 'true'
+ name: password
+ default-value: cw-aio
+ - name: create-update-user
+ parameter:
+ - name: number
+ data-type: STRING
+ mandatory: 'true'
+ - name: password
+ data-type: STRING
+ mandatory: 'true'
+ - name: delete-user
+ parameter:
+ - name: number
+ data-type: STRING
+ mandatory: 'true'
+ initial-config-primitive:
+ - name: config
+ parameter:
+ - name: proxied_ip
+ value: <rw_mgmt_ip>
+ seq: '1'
+ juju:
+ charm: clearwater-aio-proxy
+ connection-point:
+ - name: eth0
+ type: VPORT
+ - name: eth1
+ type: VPORT
+ vdu:
+ - id: IMS-ALLIN1_2p-VM
+ name: IMS-ALLIN1_2p-VM
+ description: IMS-ALLIN1_2p-VM
+ image: /mnt/powervault/virtualization/vnfs/demos/mwc2016/allin1.qcow2
+ vm-flavor:
+ memory-mb: '4096'
+ storage-gb: '10'
+ vcpu-count: '2'
+ mgmt-vpci: 0000:00:0a.0
+ external-interface:
+ - name: eth0
+ virtual-interface:
+ bandwidth: '0'
+ type: VIRTIO
+ vpci: 0000:00:0a.0
+ vnfd-connection-point-ref: eth0
+ - name: eth1
+ virtual-interface:
+ bandwidth: '0'
+ type: OM-MGMT
+ vpci: 0000:00:0b.0
+ vnfd-connection-point-ref: eth1
+ guest-epa:
+ cpu-pinning-policy: DEDICATED
+ cpu-thread-pinning-policy: PREFER
+ mempage-size: LARGE
+ numa-node-policy:
+ mem-policy: STRICT
+ node:
+ - id: '0'
+ paired-threads:
+ num-paired-threads: '1'
+ node-cnt: '1'
--- /dev/null
+# Overview
+
+This is a [Juju charm](https://jujucharms.com/about), which allows configuration of the [Project Clearwater](http://projectclearwater.org) IMS core's [all-in-one](http://clearwater.readthedocs.org/en/stable/All_in_one_Images/index.html) node.
+
+This is a proxy charm, meaning that you must spin up the all-in-one VM first, and then point this charm at it to manage it.
+
+Since the all-in-one node does not support scaling up, neither does this charm.
+
+# Deployment
+
+## Initial deployment
+
+The all-in-one VM image should be downloaded from [http://repo.cw-ngv.com/juju-clearwater-2/cw-aio.ova](http://repo.cw-ngv.com/juju-clearwater-2/cw-aio.ova) and deployed onto your virtualization platform. (You could alternatively try the latest all-in-one VM image from [http://vm-images.cw-ngv.com/](http://vm-images.cw-ngv.com/), but this may not have been tested with this charm - the juju-clearwater-2 version above is known to work.)
+
+The proxy charm should then be deployed, pointing at the all-in-one VM.
+
+# Using the All-in-One Node
+
+Once installed, the all-in-one node will listen for SIP traffic on port 5060 (both TCP and UDP). You can use a standard SIP client (e.g. Blink, Boghe or X-Lite) to register against the all-in-one VM's public IP and make calls.
+
+Our ["Making your first call" documentation](http://clearwater.readthedocs.org/en/latest/Making_your_first_call/index.html) has more information on this process.
+
+# Configuration
+
+- `proxied_ip`: The IP address of the All-in-One node to manage
+- `password`: The login password of the All-in-One node to manage (default is
+ very likely correct)
+- `home_domain`: The home domain for this service
+- `base_number`: The first number to be allocated in the number range
+- `number_count`: The count of numbers to allocate
+
+# Actions
+
+This proxy charm exposes two actions.
+
+- `create-update-user`: Creates a user, or updates if they already exist
+ - `number`: The number to provision
+ - `password`: The number's password
+
+- `delete-user`: Deletes a user
+ - `number`: The number to delete
+
+For example, `juju action do clearwater-aio-proxy/0 create-update-user number=\"1234567890\" password=secret` creates a user. (Note that the escaped double-quotes are required to avoid juju parsing the number as an integer rather than a string.)
+
+Note that the numbers specified in `create-update-user` and `delete-user` actions need not be in the number range specified in the configuration above.
+
+# Contact and Upstream Project Information
+
+Project Clearwater is an open-source IMS core, developed by [Metaswitch Networks](http://www.metaswitch.com) and released under the [GNU GPLv3](http://www.projectclearwater.org/download/license/). You can find more information about it on [our website](http://www.projectclearwater.org/) or [our documentation site](https://clearwater.readthedocs.org).
+
+Clearwater source code and issue list can be found at https://github.com/Metaswitch/.
+
+If you have problems when using Project Clearwater, read [our troubleshooting documentation](http://clearwater.readthedocs.org/en/latest/Troubleshooting_and_Recovery/index.html) for help, or see [our support page](http://clearwater.readthedocs.org/en/latest/Support/index.html) to find out how to ask mailing list questions or raise issues.
--- /dev/null
+create-update-user:
+ description: Create a user, or update a user if they already exist.
+ params:
+ number:
+ description: The number to provision
+ type: string
+ password:
+ description: The number's password
+ type: string
+ required: [number, password]
+ additionalProperties: false
+delete-user:
+ description: Delete a user. If the user does not exist, this is still considered success.
+ params:
+ number:
+ description: The number to provision
+ type: string
+ required: [number]
+ additionalProperties: false
--- /dev/null
+#!/bin/bash
+set -e
+
+# Get the configuration and action parameters.
+proxied_ip=$(config-get proxied_ip)
+login_password=$(config-get password)
+home_domain=$(config-get home_domain)
+number=$(action-get number)
+password=$(action-get password)
+
+if [ -z "$proxied_ip" ] || [ -z "$login_password" ] || [ -z "$home_domain" ] ; then
+ echo Proxy not yet configured!
+ exit 1
+fi
+
+# If the user doesn't exist, try to create them. Otherwise, try to update them.
+if ! sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S /usr/share/clearwater/bin/display_user $number $home_domain" ; then
+ echo "Subscriber doesn't exist - creating"
+ sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S /usr/share/clearwater/bin/create_user $number $home_domain $password"
+else
+ echo "Subscriber exists - updating"
+ sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S /usr/share/clearwater/bin/update_user $number $home_domain --password $password"
+fi
--- /dev/null
+#!/bin/bash
+set -e
+
+# Get the configuration and action parameters.
+proxied_ip=$(config-get proxied_ip)
+login_password=$(config-get password)
+home_domain=$(config-get home_domain)
+number=$(action-get number)
+password=$(action-get password)
+
+if [ -z "$proxied_ip" ] || [ -z "$login_password" ] || [ -z "$home_domain" ] ; then
+ echo Proxy not yet configured!
+ exit 1
+fi
+
+# Create the user.
+sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S /usr/share/clearwater/bin/create_user $number $home_domain $password"
--- /dev/null
+#!/bin/bash
+set -e
+
+# Get the configuration and action parameters.
+proxied_ip=$(config-get proxied_ip)
+login_password=$(config-get password)
+home_domain=$(config-get home_domain)
+number=$(action-get number)
+
+if [ -z "$proxied_ip" ] || [ -z "$login_password" ] || [ -z "$home_domain" ] ; then
+ echo Proxy not yet configured!
+ exit 1
+fi
+
+# Delete the user.
+sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S /usr/share/clearwater/bin/delete_user -y $number $home_domain"
--- /dev/null
+options:
+ proxied_ip:
+ description: The IP address of the All-in-One node to manage
+ type: string
+ password:
+ default: cw-aio
+ description: The login password of the All-in-One node to manage (default is very likely correct)
+ type: string
+ home_domain:
+ default: example.com
+ description: The home domain for this service
+ type: string
+ base_number:
+ default: "1230000000"
+ description: The first number to be allocated in the number range
+ type: string
+ number_count:
+ default: 1000
+ description: The count of numbers to allocate
+ type: int
--- /dev/null
+Project Clearwater - IMS in the Cloud
+Copyright (C) 2016 Metaswitch Networks Ltd
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version, along with the "Special Exception" for use of
+the program along with SSL, set forth below. This program 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 General Public License for more
+details. You should have received a copy of the GNU General Public
+License along with this program. If not, see
+<http://www.gnu.org/licenses/>.
+
+The author can be reached by email at clearwater@metaswitch.com or by
+post at Metaswitch Networks Ltd, 100 Church St, Enfield EN2 6BQ, UK
+
+Special Exception
+Metaswitch Networks Ltd grants you permission to copy, modify,
+propagate, and distribute a work formed by combining OpenSSL with The
+Software, or a work derivative of such a combination, even if such
+copying, modification, propagation, or distribution would otherwise
+violate the terms of the GPL. You must comply with the GPL in all
+respects for all of the code used other than OpenSSL.
+"OpenSSL" means OpenSSL toolkit software distributed by the OpenSSL
+Project and licensed under the OpenSSL Licenses, or a work based on such
+software and licensed under the OpenSSL Licenses.
+"OpenSSL Licenses" means the OpenSSL License and Original SSLeay License
+under which the OpenSSL Project distributes the OpenSSL toolkit software,
+as those licenses appear in the file LICENSE-OPENSSL.
--- /dev/null
+#!/bin/bash
+set -e
+
+# Get the configuration.
+proxied_ip=$(config-get proxied_ip)
+login_password=$(config-get password)
+home_domain=$(config-get home_domain)
+base_number=$(config-get base_number)
+number_count=$(config-get number_count)
+
+# If the node is configured, provision it and its numbers.
+if [ -n "$proxied_ip" ] && [ -n "$home_domain" ] && [ -n "$login_password" ] ; then
+ # Copy the reconfigure-aio script on, and run it.
+ status-set maintenance "configuring"
+ sshpass -p$login_password scp -o StrictHostKeyChecking=no $CHARM_DIR/lib/reconfigure-aio ubuntu@$proxied_ip:/tmp/reconfigure-aio.$$
+ sshpass -p$login_password ssh -o StrictHostKeyChecking=no ubuntu@$proxied_ip "echo $login_password | sudo -S bash -c 'bash /tmp/reconfigure-aio.$$ $home_domain $base_number $number_count ; rm -f /tmp/reconfigure-aio.$$'"
+ status-set active "configured"
+else
+ status-set blocked "waiting for configuration"
+fi
--- /dev/null
+#!/bin/bash
+set -e
+
+status-set maintenance "installing"
+
+# Install sshpass as we'll need it shortly
+apt-get update
+apt-get -q -y --force-yes install sshpass
+
+status-set maintenance "installed"
--- /dev/null
+#!/bin/bash
+# Here put anything that is needed to start the service.
+# Note that currently this is run directly after install
+# i.e. 'service apache2 start'
+set -e
+
+# Nothing to do
--- /dev/null
+#!/bin/bash
+# This will be run when the service is being torn down, allowing you to disable
+# it in various ways..
+# For example, if your web app uses a text file to signal to the load balancer
+# that it is live... you could remove it and sleep for a bit to allow the load
+# balancer to stop sending traffic.
+# rm /srv/webroot/server-live.txt && sleep 30
+set -e
+
+# Nothing to do
--- /dev/null
+#!/bin/bash
+# This hook is executed each time a charm is upgraded after the new charm
+# contents have been unpacked
+# Best practice suggests you execute the hooks/install to ensure all updates are processed -
+# hooks/config_change is triggered automatically
+set -e
+
+$CHARM_DIR/hooks/install
--- /dev/null
+<?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.2 r9819"
+ sodipodi:docname="icon.svg">
+ <defs
+ id="defs6519">
+ <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="1.3995495"
+ 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="1331"
+ inkscape:window-height="674"
+ inkscape:window-x="19"
+ inkscape:window-y="1"
+ inkscape:window-maximized="0"
+ 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:#e6e6e6;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" />
+ <image
+ y="633.29077"
+ x="-270"
+ id="image3169"
+ xlink:href="data:image/png;base64,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
+AABJRU5ErkJggg==
+"
+ height="96"
+ width="96"
+ style="stroke:#000000;stroke-opacity:0.18666669" />
+ </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 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 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 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 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 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 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 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
--- /dev/null
+#!/bin/bash
+# Reconfigures an all-in-one image to use a new home domain and number range.
+# Usage: reconfigure-aio <home-domain> [<base-number> <number-count>]
+
+# Get command-line arguments.
+home_domain=$1
+base_number=$2
+number_count=$3
+
+if [ -z "$home_domain" ] ; then
+ echo "Usage: reconfigure-aio <home-domain> [<base-number> <number-count>]"
+fi
+
+# Remove all old numbers from the database, unless they're currently assigned.
+# We do this even if the home domain hasn't changed, because the number range might have done (and
+# it's hard to tell if that's happened, and cheap/low-impact to just do the reprovisioning).
+old_home_domain=$(. /etc/clearwater/config ; echo $home_domain)
+echo "DELETE FROM ellis.numbers WHERE number LIKE '%@$old_home_domain' AND owner_id IS NULL ;" | mysql
+
+# Update /etc/clearwater/shared_config, if the home domain has changed.
+if [ "$home_domain" != "$old_home_domain" ] ; then
+ function escape { echo $1 | sed -e 's/\//\\\//g' ; }
+ sed -e 's/^home_domain=.*$/home_domain='$(escape $home_domain)'/g' \
+ </etc/clearwater/shared_config >/tmp/shared_config.$$
+ mv /tmp/shared_config.$$ /etc/clearwater/shared_config
+
+ # Restart clearwater-infrastructure to propagate changes to other configuration files.
+ service clearwater-infrastructure restart
+fi
+
+# Create new numbers in the new domain, if we've been asked to.
+if [ -n "$base_number" ] && [ -n "$number_count" ] ; then
+ /usr/share/clearwater/ellis/env/bin/python /usr/share/clearwater/ellis/src/metaswitch/ellis/tools/create_numbers.py --start $base_number --count $number_count
+fi
+
+# Restart all the components, if the home domain has changed.
+if [ "$home_domain" != "$old_home_domain" ] ; then
+ # Work around https://github.com/Metaswitch/sprout/issues/1296.
+ service bono stop
+
+ # Restart all the processes.
+ for X in /usr/share/clearwater/infrastructure/scripts/restart/* ; do $X ; done
+
+ # Kick monit to wake up and sleep for 10 seconds to make sure it has an accurate view of the system.
+ monit
+ sleep 10
+
+ # Now wait until all the processes are back up and running (or at least "Uptime failed", which
+ # means the process is running, just hasn't been running for very long).
+ while monit summary | grep _process | egrep -v "(Running|Uptime failed)" ; do
+ echo Some processes still not running - waiting...
+ sleep 2
+ done
+ echo All processes running - configuration complete!
+fi
--- /dev/null
+name: clearwater-aio-proxy
+summary: All-in-One proxy charm for Project Clearwater
+maintainer: Project Clearwater Maintainers <maintainers@projectclearwater.org>
+description: All-in-One proxy charm for Project Clearwater
+tags:
+ - misc
+subordinate: false
+provides:
+ ue:
+ interface: 3GPP-Gm
--- /dev/null
+vnfd:vnfd-catalog:
+ vnfd:
+ - id: mwc16gen
+ name: mwc16gen
+ short-name: mwc16gen
+ description: tidgen 4x10Gbps 28GB 11cores
+ logo: tef.png
+ mgmt-interface:
+ vdu-id: mwc16gen1-VM
+ connection-point:
+ - name: eth0
+ type: VPORT
+ - name: eth1
+ type: VPORT
+ - name: xe0
+ type: VPORT
+ - name: xe1
+ type: VPORT
+ - name: xe2
+ type: VPORT
+ - name: xe3
+ type: VPORT
+ vdu:
+ - id: mwc16gen1-VM
+ name: mwc16gen1-VM
+ description: tidgen with 4x10Gbps 28GB
+ image: /mnt/powervault/virtualization/vnfs/demos/mwc2016/tidgen_mwc16.qcow2
+ vm-flavor:
+ memory-mb: '28672'
+ mgmt-vpci: 0000:00:0a.0
+ external-interface:
+ - name: xe0
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:10.0'
+ vnfd-connection-point-ref: xe0
+ - name: xe1
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:11.0'
+ vnfd-connection-point-ref: xe1
+ - name: xe2
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:12.0'
+ vnfd-connection-point-ref: xe2
+ - name: xe3
+ virtual-interface:
+ type: PCI-PASSTHROUGH
+ vpci: '0000:00:13.0'
+ vnfd-connection-point-ref: xe3
+ - name: eth0
+ virtual-interface:
+ bandwidth: '1000000'
+ type: VIRTIO
+ vpci: 0000:00:0a.0
+ vnfd-connection-point-ref: eth0
+ - name: eth1
+ virtual-interface:
+ bandwidth: '1000000'
+ type: OM-MGMT
+ vpci: 0000:00:0b.0
+ vnfd-connection-point-ref: eth1
+ guest-epa:
+ cpu-pinning-policy: DEDICATED
+ cpu-thread-pinning-policy: PREFER
+ mempage-size: LARGE
+ numa-node-policy:
+ mem-policy: STRICT
+ node:
+ - id: '0'
+ paired-threads:
+ num-paired-threads: '11'
+ node-cnt: '1'
+ host-epa:
+ om-cpu-feature:
+ - 64b
+ - iommu
+ - lps
+ - tlbps
+ - hwsv
+ - dioc
+ - ht
+ om-cpu-model-string: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz
+ hypervisor-epa:
+ type: REQUIRE_KVM
+ version: 10002|12001|2.6.32-358.el6.x86_64