blob: 750115cdd25d3e5d50aa761e4810adcabff35b69 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2016-2017 RIFT.IO Inc
#
# 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.
#
import argparse
import itertools
import logging
import os
import sys
import tempfile
import uuid
import yaml
import gi
gi.require_version('RwYang', '1.0')
gi.require_version('RwProjectVnfdYang', '1.0')
gi.require_version('RwProjectNsdYang', '1.0')
gi.require_version('RwProjectYang', '1.0')
from gi.repository import (
RwYang,
RwProjectVnfdYang as RwVnfdYang,
RwProjectNsdYang as RwNsdYang,
RwProjectYang,
)
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger("openmano2rift.py")
class UnknownVNFError(Exception):
pass
class DescriptorFileWriter(object):
def __init__(self, module_list, output_dir, output_format):
self._model = RwYang.Model.create_libncx()
for module in module_list:
self._model.load_module(module)
self._output_dir = output_dir
self._output_format = output_format
def _write_file(self, file_name, output):
file_path = os.path.join(self._output_dir, file_name)
dir_path = os.path.dirname(file_path)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
with open(file_path, "w") as hdl:
hdl.write(output)
logger.info("Wrote descriptor to %s", file_path)
def _write_json(self, descriptor, subdir):
self._write_file(
'%s.json' % os.path.join(descriptor.name, subdir, descriptor.name),
descriptor.descriptor.to_json(self._model)
)
def _write_xml(self, descriptor, subdir):
self._write_file(
'%s.xml' % os.path.join(descriptor.name, subdir, descriptor.name),
descriptor.descriptor.to_xml_v2(self._model, pretty_print=True)
)
def _write_yaml(self, descriptor, subdir):
self._write_file(
'%s.yaml' % os.path.join(descriptor.name, subdir, descriptor.name),
yaml.dump(descriptor.descriptor.as_dict()),
)
def write_descriptor(self, descriptor, subdir=""):
if self._output_format == 'json':
self._write_json(descriptor, subdir=subdir)
elif self._output_format == 'xml':
self._write_xml(descriptor, subdir=subdir)
elif self._output_format == 'yaml':
self._write_yaml(descriptor, subdir=subdir)
class RiftManoDescriptor(object):
def __init__(self, openmano=None):
self.openmano = openmano
self.descriptor = None
class RiftNS(RiftManoDescriptor):
def __init__(self, openmano=None):
super().__init__(openmano)
self.nsd_catalog = None
self.nsd = None
self.name = None
def get_vnfd_id(self, vnf_list, vnf_name):
for vnf in vnf_list:
if vnf.name == vnf_name:
return vnf.vnfd.id
# Didn't find the vnf just return the vnf_name
return vnf_name
def openmano2rift(self, vnf_list):
self.descriptor = RwNsdYang.YangData_RwProject_Project_NsdCatalog()
openmano_nsd = self.openmano.dictionary
self.name = openmano_nsd['name']
nsd = self.descriptor.nsd.add()
nsd.id = str(uuid.uuid1())
nsd.name = self.name
nsd.short_name = self.name
nsd.description = openmano_nsd['description']
nodes = openmano_nsd['topology']['nodes']
connections = openmano_nsd['topology']['connections']
def create_consituent_vnfds():
vnf_member_index_dict = {}
vnfd_idx_gen = itertools.count(1)
for key in nodes:
node = nodes[key]
if node['type'] != 'VNF':
continue
vnfd_idx = next(vnfd_idx_gen)
constituent_vnfd = nsd.constituent_vnfd.add()
constituent_vnfd.member_vnf_index = vnfd_idx
constituent_vnfd.vnfd_id_ref = self.get_vnfd_id(vnf_list, node['VNF model'])
vnf_member_index_dict[key] = vnfd_idx
return vnf_member_index_dict
def create_connections(vnf_member_index_dict):
keys = connections.keys()
for key in keys:
# TODO: Need clarification from TEF
# skip the mgmtnet in OpenMANO descriptor
if key == 'mgmtnet':
continue
conn = connections[key]
vld = nsd.vld.add()
vld.from_dict(dict(
id=str(uuid.uuid1()),
name=key,
short_name=key,
type_yang='ELAN',
))
nodes = conn['nodes']
for node, node_keys in [(node, node.keys()) for node in nodes]:
for node_key in node_keys:
topo_node = openmano_nsd['topology']['nodes'][node_key]
if topo_node['type'] == 'VNF':
cpref = vld.vnfd_connection_point_ref.add()
cpref.from_dict(dict(
member_vnf_index_ref=vnf_member_index_dict[node_key],
vnfd_id_ref=self.get_vnfd_id(vnf_list, topo_node['VNF model']),
#vnfd_id_ref=topo_node['VNF model'],
vnfd_connection_point_ref=node[node_key],
))
if key != 'control-net':
vld.provider_network.physical_network = 'physnet_sriov'
vld.provider_network.overlay_type = 'VLAN'
vnf_member_index_dict = create_consituent_vnfds()
create_connections(vnf_member_index_dict)
class RiftVnfd(RiftManoDescriptor):
def __init__(self, openmano=None):
super().__init__(openmano)
self.vnfd_catalog = None
self.vnfd = None
def find_external_connection(self, vdu_name, if_name):
"""
Find if the vdu interface has an external connection.
"""
openmano_vnfd = self.openmano.dictionary['vnf']
if 'external-connections' not in openmano_vnfd:
return None
ext_conn_list = openmano_vnfd['external-connections']
for ext_conn in ext_conn_list:
if ((ext_conn['VNFC'] == vdu_name) and
(ext_conn['local_iface_name'] == if_name)):
return ext_conn
return None
def openmano2rift(self):
self.descriptor = RwVnfdYang.YangData_RwProject_Project_VnfdCatalog()
vnfd = self.descriptor.vnfd.add()
self.vnfd = vnfd
vnfd.id = str(uuid.uuid1())
openmano_vnfd = self.openmano.dictionary['vnf']
self.name = openmano_vnfd['name']
vnfd.name = self.name
if "description" in openmano_vnfd:
vnfd.description = openmano_vnfd['description']
# Parse and add all the external connection points
if 'external-connections' in openmano_vnfd:
ext_conn_list = openmano_vnfd['external-connections']
for ext_conn in ext_conn_list:
# TODO: Fix this
if ext_conn['name'] == 'eth0':
continue
conn_point = vnfd.connection_point.add()
conn_point.name = ext_conn['name']
conn_point.type_yang = 'VPORT'
# TODO: Need a concrete example of how openmano descriptor
# uses internal connections.
if 'internal-connections' in openmano_vnfd:
int_conn_list = openmano_vnfd['internal-connections']
def add_external_interfaces(vdu, numa):
if 'interfaces' not in numa:
return
numa_if_list = numa['interfaces']
for numa_if in numa_if_list:
ext_conn = self.find_external_connection(vdu.name, numa_if['name'])
if not ext_conn:
continue
ext_iface = vdu.external_interface.add()
ext_iface.name = numa_if['name']
ext_iface.vnfd_connection_point_ref = ext_conn['name']
ext_iface.virtual_interface.vpci = numa_if['vpci']
if numa_if['dedicated'] == 'no':
ext_iface.virtual_interface.type_yang = 'SR_IOV'
else:
ext_iface.virtual_interface.type_yang = 'PCI_PASSTHROUGH'
vnfc_list = openmano_vnfd['VNFC']
for vnfc in vnfc_list:
vdu = vnfd.vdu.add()
vdu_dict = dict(
id=str(uuid.uuid1()),
name=vnfc['name'],
image=vnfc['VNFC image'],
vm_flavor={"storage_gb": vnfc["disk"] if "disk" in vnfc else 20},
)
if "description" in vnfc:
vdu_dict["description"] = vnfc['description']
vdu.from_dict(vdu_dict)
vnfd.mgmt_interface.vdu_id = vdu.id
numa_list = vnfc['numas']
memory = 0
vcpu_count = 0
numa_node_cnt = 0
for numa in numa_list:
node = vdu.guest_epa.numa_node_policy.node.add()
node.id = numa_node_cnt
# node.memory_mb = int(numa['memory']) * 1024
numa_node_cnt += 1
memory = memory + node.memory_mb
# Need a better explanation of "cores", "paired-threads", "threads"
# in openmano descriptor. Particularly how they map to cpu and
# thread pinning policies
if 'paired-threads' in numa:
vcpu_count = vcpu_count + int(numa['paired-threads']) * 2
if 'cores' in numa:
vcpu_count = vcpu_count + int(numa['cores'])
add_external_interfaces(vdu, numa)
# vdu.vm_flavor.memory_mb = memory
vdu.vm_flavor.memory_mb = 12 * 1024
vdu.vm_flavor.vcpu_count = vcpu_count
vdu.guest_epa.numa_node_policy.node_cnt = numa_node_cnt
vdu.guest_epa.numa_node_policy.mem_policy = 'STRICT'
vdu.guest_epa.mempage_size = 'LARGE'
vdu.guest_epa.cpu_pinning_policy = 'DEDICATED'
vdu.guest_epa.cpu_thread_pinning_policy = 'PREFER'
# TODO: Enable hypervisor epa
# vdu.hypervisor_epa.version = vnfc['hypervisor']['version']
# if vnfc['hypervisor']['type'] == 'QEMU-kvm':
# vdu.hypervisor_epa.type_yang = 'REQUIRE_KVM'
# else:
# vdu.hypervisor_epa.type_yang = 'PREFER_KVM'
# TODO: Enable host epa
# vdu.host_epa.cpu_feature = vnfc['processor']['features']
# Parse the bridge interfaces
if 'bridge-ifaces' in vnfc:
bridge_ifaces = vnfc['bridge-ifaces']
for bridge_iface in bridge_ifaces:
# TODO: Fix this
if bridge_iface['name'] == 'eth0':
continue
ext_conn = self.find_external_connection(vdu.name,
bridge_iface['name'])
if ext_conn:
ext_iface = vdu.external_interface.add()
ext_iface.name = bridge_iface['name']
ext_iface.vnfd_connection_point_ref = ext_conn['name']
if 'vpci' in bridge_iface:
ext_iface.virtual_interface.vpci = bridge_iface['vpci']
ext_iface.virtual_interface.type_yang = 'VIRTIO'
# set vpci information for the 'default' network
# TODO: This needs to be inferred gtom bridge ifaces,
# need input from TEF
vdu.mgmt_vpci = "0000:00:0a.0"
class OpenManoDescriptor(object):
def __init__(self, yaml_file_hdl):
self.dictionary = yaml.load(yaml_file_hdl)
@property
def type(self):
""" The descriptor type (ns or vnf)"""
if 'vnf' in self.dictionary:
return "vnf"
else:
return "ns"
def dump(self):
""" Dump the Descriptor out to stdout """
print(yaml.dump(self.dictionary))
def is_writable_directory(dir_path):
""" Returns True if dir_path is writable, False otherwise
Arguments:
dir_path - A directory path
"""
if not os.path.exists(dir_path):
raise ValueError("Directory does not exist: %s", dir_path)
try:
testfile = tempfile.TemporaryFile(dir=dir_path)
testfile.close()
except OSError:
return False
return True
def create_vnfs_from_yaml_files(yaml_file_hdls):
""" Create a list of RiftVnfd instances from yaml file handles
Arguments:
yaml_file_hdls - OpenMano Yaml file handles
Returns:
A list of RiftVnfd instances
"""
vnf_list = []
for yaml_file_hdl in yaml_file_hdls:
openmano = OpenManoDescriptor(yaml_file_hdl)
yaml_file_hdl.seek(0)
if openmano.type != "vnf":
continue
vnf = RiftVnfd(openmano)
vnf.openmano2rift()
vnf_list.append(vnf)
return vnf_list
def create_ns_from_yaml_files(yaml_file_hdls, vnf_list):
""" Create a list of RiftNS instances from yaml file handles
Arguments:
yaml_file_hdls - OpenMano Yaml file handles
vnf_list - list of RiftVnfd
Returns:
A list of RiftNS instances
"""
ns_list = []
for yaml_file_hdl in yaml_file_hdls:
openmano = OpenManoDescriptor(yaml_file_hdl)
if openmano.type != "ns":
continue
net_svc = RiftNS(openmano)
net_svc.openmano2rift(vnf_list)
ns_list.append(net_svc)
return ns_list
def parse_args(argv=sys.argv[1:]):
""" Parse the command line arguments
Arguments:
arv - The list of arguments to parse
Returns:
Argparse Namespace instance
"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-o', '--outdir',
default='.',
help="Directory to output converted descriptors",
)
parser.add_argument(
'-f', '--format',
choices=['yaml', 'xml', 'json'],
default='xml',
help="Descriptor output format",
)
parser.add_argument(
'yaml_file_hdls',
metavar="yaml_file",
nargs="+",
type=argparse.FileType('r'),
help="OpenMano YAML Descriptor File",
)
args = parser.parse_args(argv)
if not os.path.exists(args.outdir):
os.makedirs(args.outdir)
if not is_writable_directory(args.outdir):
logging.error("Directory %s is not writable", args.outdir)
sys.exit(1)
return args
def main(argv=sys.argv[1:]):
args = parse_args(argv)
vnf_list = create_vnfs_from_yaml_files(args.yaml_file_hdls)
ns_list = create_ns_from_yaml_files(args.yaml_file_hdls, vnf_list)
# TODO (Philip): Relook at the model generation
writer = DescriptorFileWriter(
module_list=['rw-project', 'project-nsd', 'rw-project-nsd', 'project-vnfd', 'rw-project-vnfd'],
output_dir=args.outdir,
output_format=args.format,
)
for nw_svc in ns_list:
writer.write_descriptor(nw_svc, subdir="nsd")
for vnf in vnf_list:
writer.write_descriptor(vnf, subdir="vnfd")
if __name__ == "__main__":
main()