Feature 8047: osmclient package creation and validation tool 92/8092/31
authorFelipe Vicens <felipe.vicens@atos.net>
Fri, 25 Oct 2019 14:42:41 +0000 (16:42 +0200)
committerFelipe Vicens <felipe.vicens@atos.net>
Mon, 25 Nov 2019 15:41:21 +0000 (16:41 +0100)
Change-Id: Ie1e0be7f5029a4b323fa585f8aa39631568176ac
Signed-off-by: Felipe Vicens <felipe.vicens@atos.net>
MANIFEST.in [new file with mode: 0644]
osmclient/common/package_tool.py [new file with mode: 0644]
osmclient/scripts/osm.py
osmclient/sol005/client.py
osmclient/templates/nsd.yaml.j2 [new file with mode: 0644]
osmclient/templates/nst.yaml.j2 [new file with mode: 0644]
osmclient/templates/vnfd.yaml.j2 [new file with mode: 0644]
osmclient/v1/client.py
setup.py
test-requirements.txt [new file with mode: 0644]
tox.ini

diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..4deac18
--- /dev/null
@@ -0,0 +1,17 @@
+# Copyright 2019 ATOS
+#
+# All Rights Reserved.
+#
+#    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.
+
+include osmclient/templates/*.yaml.j2
\ No newline at end of file
diff --git a/osmclient/common/package_tool.py b/osmclient/common/package_tool.py
new file mode 100644 (file)
index 0000000..7cc10be
--- /dev/null
@@ -0,0 +1,286 @@
+# /bin/env python3
+# Copyright 2019 ATOS
+#
+# All Rights Reserved.
+#
+#    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.
+
+from osmclient.common.exceptions import ClientException
+import os
+import glob
+import time
+import tarfile
+import hashlib
+from osm_im.validation import Validation as validation_im
+from jinja2 import Environment, PackageLoader
+
+
+class PackageTool(object):
+    def __init__(self, client=None):
+        self._client = client
+
+    def create(self, package_type, base_directory, package_name, override, image, vdus, vcpu, memory, storage,
+               interfaces, vendor, detailed, netslice_subnets, netslice_vlds):
+        """
+            **Create a package descriptor**
+
+            :params:
+                - package_type: [vnf, ns, nst]
+                - base directory: path of destination folder
+                - package_name: is the name of the package to be created
+                - image: specify the image of the vdu
+                - vcpu: number of virtual cpus of the vdu
+                - memory: amount of memory in MB pf the vdu
+                - storage: amount of storage in GB of the vdu
+                - interfaces: number of interfaces besides management interface
+                - vendor: vendor name of the vnf/ns
+                - detailed: include all possible values for NSD, VNFD, NST
+                - netslice_subnets: number of netslice_subnets for the NST
+                - netslice_vlds: number of virtual link descriptors for the NST
+
+            :return: status
+        """
+
+        # print("location: {}".format(osmclient.__path__))
+        file_loader = PackageLoader("osmclient")
+        env = Environment(loader=file_loader)
+        if package_type == 'ns':
+            template = env.get_template('nsd.yaml.j2')
+            content = {"name": package_name, "vendor": vendor, "vdus": vdus, "clean": False, "interfaces": interfaces,
+                       "detailed": detailed}
+        elif package_type == 'vnf':
+            template = env.get_template('vnfd.yaml.j2')
+            content = {"name": package_name, "vendor": vendor, "vdus": vdus, "clean": False, "interfaces": interfaces,
+                       "image": image, "vcpu": vcpu, "memory": memory, "storage": storage, "detailed": detailed}
+        elif package_type == 'nst':
+            template = env.get_template('nst.yaml.j2')
+            content = {"name": package_name, "vendor": vendor, "interfaces": interfaces,
+                       "netslice_subnets": netslice_subnets, "netslice_vlds": netslice_vlds, "detailed": detailed}
+        else:
+            raise ClientException("Wrong descriptor type {}. Options: ns, vnf, nst".format(package_type))
+
+        # print("To be rendered: {}".format(content))
+        output = template.render(content)
+        # print(output)
+
+        structure = self.discover_folder_structure(base_directory, package_name, override)
+        if structure.get("folders"):
+            self.create_folders(structure["folders"], package_type)
+        if structure.get("files"):
+            self.create_files(structure["files"], output, package_type)
+        return "Created"
+
+    def validate(self, base_directory):
+        """
+            **Validate OSM Descriptors given a path**
+
+            :params:
+                - base_directory is the root path for all descriptors
+
+            :return: List of dict of validated descriptors. keys: type, path, valid, error
+        """
+        table = []
+        descriptors_paths = [f for f in glob.glob(base_directory + "/**/*.yaml", recursive=True)]
+        print("Base directory: {}".format(base_directory))
+        print("{} Descriptors found to validate".format(len(descriptors_paths)))
+        for desc_path in descriptors_paths:
+            with open(desc_path) as descriptor_file:
+                descriptor_data = descriptor_file.read()
+            desc_type = "-"
+            try:
+                desc_type, descriptor_data = validation_im.yaml_validation(self, descriptor_data)
+                validation_im.pyangbind_validation(self, desc_type, descriptor_data)
+                table.append({"type": desc_type, "path": desc_path, "valid": "OK", "error": "-"})
+            except Exception as e:
+                table.append({"type": desc_type, "path": desc_path, "valid": "ERROR", "error": str(e)})
+        return table
+
+    def build(self, package_folder, skip_validation=True):
+        """
+            **Creates a .tar.gz file given a package_folder**
+
+            :params:
+                - package_folder: is the name of the folder to be packaged
+                - skip_validation: is the flag to validate or not the descriptors on the folder before build
+
+            :returns: message result for the build process
+        """
+
+        if not os.path.exists("{}".format(package_folder)):
+            return "Fail, package is not in the specified route"
+        if not skip_validation:
+            results = self.validate(package_folder)
+            for result in results:
+                if result["valid"] != "OK":
+                    return("There was an error validating the file: {} with error: {}".format(result["path"],
+                                                                                              result["error"]))
+        self.calculate_checksum(package_folder)
+        with tarfile.open("{}.tar.gz".format(package_folder), mode='w:gz') as archive:
+            print("Adding File: {}".format(package_folder))
+            archive.add('{}'.format(package_folder), recursive=True)
+        return "Created {}.tar.gz".format(package_folder)
+
+    def calculate_checksum(self, package_folder):
+        """
+            **Function to calculate the checksum given a folder**
+
+            :params:
+                - package_folder: is the folder where we have the files to calculate the checksum
+            :returns: None
+        """
+        files = [f for f in glob.glob(package_folder + "/**/*.*", recursive=True)]
+        checksum = open("{}/checksum.txt".format(package_folder), "w+")
+        for file_item in files:
+            if "checksum.txt" in file_item:
+                continue
+            # from https://www.quickprogrammingtips.com/python/how-to-calculate-md5-hash-of-a-file-in-python.html
+            md5_hash = hashlib.md5()
+            with open(file_item, "rb") as f:
+                # Read and update hash in chunks of 4K
+                for byte_block in iter(lambda: f.read(4096), b""):
+                    md5_hash.update(byte_block)
+                checksum.write("{}\t{}\n".format(md5_hash.hexdigest(), file_item))
+        checksum.close()
+
+    def create_folders(self, folders, package_type):
+        """
+            **Create folder given a list of folders**
+
+            :params:
+                - folders: [List] list of folders paths to be created
+                - package_type: is the type of package to be created
+            :return: None
+        """
+
+        for folder in folders:
+            try:
+                # print("Folder {} == package_type {}".format(folder[1], package_type))
+                if folder[1] == package_type:
+                    print("Creating folder:\t{}".format(folder[0]))
+                    os.makedirs(folder[0])
+            except FileExistsError:
+                pass
+
+    def save_file(self, file_name, file_body):
+        """
+            **Create a file given a name and the content**
+
+            :params:
+                - file_name: is the name of the file with the relative route
+                - file_body: is the content of the file
+            :return: None
+        """
+        print("Creating file:  \t{}".format(file_name))
+        try:
+            with open(file_name, "w+") as f:
+                f.write(file_body)
+        except Exception as e:
+            raise ClientException(e)
+
+    def generate_readme(self):
+        """
+            **Creates the README content**
+
+            :returns: readme content
+        """
+        return """# Descriptor created by OSM descriptor package generated\n\n**Created on {} **""".format(
+               time.strftime("%m/%d/%Y, %H:%M:%S", time.localtime()))
+
+    def generate_cloud_init(self):
+        """
+            **Creates the cloud-init content**
+
+            :returns: cloud-init content
+        """
+        return "---\n#cloud-config"
+
+    def create_files(self, files, file_content, package_type):
+        """
+            **Creates the files given the file list and type**
+
+            :params:
+                - files: is the list of files structure
+                - file_content: is the content of the descriptor rendered by the template
+                - package_type: is the type of package to filter the creation structure
+
+            :return: None
+        """
+        for file_item, file_package, file_type in files:
+            if package_type == file_package:
+                if file_type == "descriptor":
+                    self.save_file(file_item, file_content)
+                elif file_type == "readme":
+                    self.save_file(file_item, self.generate_readme())
+                elif file_type == "cloud_init":
+                    self.save_file(file_item, self.generate_cloud_init())
+
+    def check_files_folders(self, path_list, override):
+        """
+            **Find files and folders missing given a directory structure {"folders": [], "files": []}**
+
+            :params:
+                - path_list: is the list of files and folders to be created
+                - override: is the flag used to indicate the creation of the list even if the file exist to override it
+
+            :return: Missing paths Dict
+        """
+        missing_paths = {}
+        folders = []
+        files = []
+        for folder in path_list.get("folders"):
+            if not os.path.exists(folder[0]):
+                folders.append(folder)
+        missing_paths["folders"] = folders
+
+        for file_item in path_list.get("files"):
+            if not os.path.exists(file_item[0]) or override is True:
+                files.append(file_item)
+        missing_paths["files"] = files
+
+        return missing_paths
+
+    def discover_folder_structure(self, base_directory, name, override):
+        """
+            **Discover files and folders structure for OSM descriptors given a base_directory and name**
+
+            :params:
+                - base_directory: is the location of the package to be created
+                - name: is the name of the package
+                - override: is the flag used to indicate the creation of the list even if the file exist to override it
+            :return: Files and Folders not found. In case of override, it will return all file list
+        """
+        prefix = "{}/{}".format(base_directory, name)
+        files_folders = {"folders": [("{}_ns".format(prefix), "ns"),
+                                     ("{}_ns/icons".format(prefix), "ns"),
+                                     ("{}_ns/charms".format(prefix), "ns"),
+                                     ("{}_vnf".format(name), "vnf"),
+                                     ("{}_vnf/charms".format(prefix), "vnf"),
+                                     ("{}_vnf/cloud_init".format(prefix), "vnf"),
+                                     ("{}_vnf/images".format(prefix), "vnf"),
+                                     ("{}_vnf/icons".format(prefix), "vnf"),
+                                     ("{}_vnf/scripts".format(prefix), "vnf"),
+                                     ("{}_nst".format(prefix), "nst"),
+                                     ("{}_nst/icons".format(prefix), "nst")
+                                     ],
+                         "files": [("{}_ns/{}_nsd.yaml".format(prefix, name), "ns", "descriptor"),
+                                   ("{}_ns/README.md".format(prefix), "ns", "readme"),
+                                   ("{}_vnf/{}_vnfd.yaml".format(prefix, name), "vnf", "descriptor"),
+                                   ("{}_vnf/cloud_init/cloud-config.txt".format(prefix), "vnf", "cloud_init"),
+                                   ("{}_vnf/README.md".format(prefix), "vnf", "readme"),
+                                   ("{}_nst/{}_nst.yaml".format(prefix, name), "nst", "descriptor"),
+                                   ("{}_nst/README.md".format(prefix), "nst", "readme")
+                                   ]
+                         }
+        missing_files_folders = self.check_files_folders(files_folders, override)
+        # print("Missing files and folders: {}".format(missing_files_folders))
+        return missing_files_folders
index ee102e6..b1df0a5 100755 (executable)
@@ -3001,6 +3001,152 @@ def role_show(ctx, name):
     table.align = 'l'
     print(table)
 
+
+@cli.command(name='package-create',
+             short_help='Create a package descriptor')
+@click.argument('package-type')
+@click.argument('package-name')
+@click.option('--base-directory',
+              default='.',
+              help=('(NS/VNF/NST) Set the location for package creation. Default: "."'))
+@click.option('--image',
+              default="image-name",
+              help='(VNF) Set the name of the vdu image. Default "image-name"')
+@click.option('--vdus',
+              default=1,
+              help='(VNF) Set the number of vdus in a VNF. Default 1')
+@click.option('--vcpu',
+              default=1,
+              help='(VNF) Set the number of virtual CPUs in a vdu. Default 1')
+@click.option('--memory',
+              default=1024,
+              help='(VNF) Set the memory size (MB) of the vdu. Default 1024')
+@click.option('--storage',
+              default=10,
+              help='(VNF) Set the disk size (GB) of the vdu. Default 10')
+@click.option('--interfaces',
+              default=0,
+              help='(VNF) Set the number of additional interfaces apart from the management interface. Default 0')
+@click.option('--vendor',
+              default="OSM",
+              help='(NS/VNF) Set the descriptor vendor. Default "OSM"')
+@click.option('--override',
+              default=False,
+              is_flag=True,
+              help='(NS/VNF/NST) Flag for overriding the package if exists.')
+@click.option('--detailed',
+              is_flag=True,
+              default=False,
+              help='(NS/VNF/NST) Flag for generating descriptor .yaml with all possible commented options')
+@click.option('--netslice-subnets',
+              default=1,
+              help='(NST) Number of netslice subnets. Default 1')
+@click.option('--netslice-vlds',
+              default=1,
+              help='(NST) Number of netslice vlds. Default 1')
+@click.pass_context
+def package_create(ctx,
+                   package_type,
+                   base_directory,
+                   package_name,
+                   override,
+                   image,
+                   vdus,
+                   vcpu,
+                   memory,
+                   storage,
+                   interfaces,
+                   vendor,
+                   detailed,
+                   netslice_subnets,
+                   netslice_vlds):
+    """
+    Creates an OSM NS, VNF, NST package
+
+    \b
+    PACKAGE_TYPE: Package to be created: NS, VNF or NST.
+    PACKAGE_NAME: Name of the package to create the folder with the content.
+    """
+
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        print("Creating the {} structure: {}/{}".format(package_type.upper(), base_directory, package_name))
+        resp = ctx.obj.package_tool.create(package_type,
+                                           base_directory,
+                                           package_name,
+                                           override=override,
+                                           image=image,
+                                           vdus=vdus,
+                                           vcpu=vcpu,
+                                           memory=memory,
+                                           storage=storage,
+                                           interfaces=interfaces,
+                                           vendor=vendor,
+                                           detailed=detailed,
+                                           netslice_subnets=netslice_subnets,
+                                           netslice_vlds=netslice_vlds)
+        print(resp)
+    except ClientException as inst:
+        print("ERROR: {}".format(inst))
+        exit(1)
+
+@cli.command(name='package-validate',
+             short_help='Validate a package descriptor')
+@click.argument('base-directory',
+                default=".",
+                required=False)
+@click.pass_context
+def package_validate(ctx,
+                     base_directory):
+    """
+    Validate descriptors given a base directory.
+
+    \b
+    BASE_DIRECTORY: Stub folder for NS, VNF or NST package.
+    """
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        results = ctx.obj.package_tool.validate(base_directory)
+        table = PrettyTable()
+        table.field_names = ["TYPE", "PATH", "VALID", "ERROR"]
+        # Print the dictionary generated by the validation function
+        for result in results:
+            table.add_row([result["type"], result["path"], result["valid"], result["error"]])
+        table.sortby = "VALID"
+        table.align["PATH"] = "l"
+        table.align["TYPE"] = "l"
+        table.align["ERROR"] = "l"
+        print(table)
+    except ClientException as inst:
+        print("ERROR: {}".format(inst))
+        exit(1)
+
+@cli.command(name='package-build',
+             short_help='Build the tar.gz of the package')
+@click.argument('package-folder')
+@click.option('--skip-validation',
+              default=False,
+              is_flag=True,
+              help='skip package validation')
+@click.pass_context
+def package_build(ctx,
+                  package_folder,
+                  skip_validation):
+    """
+    Build the package NS, VNF given the package_folder.
+
+    \b
+    PACKAGE_FOLDER: Folder of the NS, VNF or NST to be packaged
+    """
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        results = ctx.obj.package_tool.build(package_folder, skip_validation)
+        print(results)
+    except ClientException as inst:
+        print("ERROR: {}".format(inst))
+        exit(1)
+
+
 if __name__ == '__main__':
     try:
         cli()
index a4f952c..6281ffe 100644 (file)
@@ -35,6 +35,7 @@ from osmclient.sol005 import user as usermodule
 from osmclient.sol005 import role
 from osmclient.sol005 import pdud
 from osmclient.common.exceptions import ClientException
+from osmclient.common import package_tool
 import json
 
 
@@ -86,10 +87,12 @@ class Client(object):
         self.user = usermodule.User(self._http_client, client=self)
         self.role = role.Role(self._http_client, client=self)
         self.pdu = pdud.Pdu(self._http_client, client=self)
+
         '''
         self.vca = vca.Vca(http_client, client=self, **kwargs)
         self.utils = utils.Utils(http_client, **kwargs)
         '''
+        self.package_tool = package_tool.PackageTool(client=self)
 
     def get_token(self):
         if self._token is None:
diff --git a/osmclient/templates/nsd.yaml.j2 b/osmclient/templates/nsd.yaml.j2
new file mode 100644 (file)
index 0000000..0e0c7a3
--- /dev/null
@@ -0,0 +1,193 @@
+# Copyright 2019 ETSI OSM
+#
+# All Rights Reserved.
+#
+#    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.
+
+nsd-catalog:
+    schema-version: "v3.0"
+    nsd:
+    -   id: {{ name }}_nsd
+        name: {{ name }}_nsd
+        short-name: {{ name }}_nsd
+        vendor: {{ vendor }}
+        description: Generated by OSM package generator
+        version: '1.0'
+        {%- if detailed is sameas true %}
+
+        # Place the logo as png in icons directory and provide the name here
+        # logo: <update, optional> Example: icons/logo.png
+        {%- endif %}
+
+        # Specify the VNFDs that are part of this NSD
+        constituent-vnfd:
+        # The member-vnf-index needs to be unique, starting from 1
+        # vnfd-id-ref is the id of the VNFD
+        # Multiple constituent VNFDs can be specified
+        -   member-vnf-index: "1"
+            vnfd-id-ref: {{ name }}_vnfd
+            {%- if detailed is sameas true %}
+            # start-by-default: True
+            {%- endif %}
+
+        # Networks for the VNFs
+        vld:
+        -   id: {{ name }}_nsd_vld0
+            name: mgmt
+            short-name: mgmt
+            type: ELAN
+            mgmt-network: true
+            {%- if detailed is sameas true %}
+            # description: <update>
+            # vim-network-name: <update> Name of network in VIM account
+            # ip-profile-ref: <update> Name of reference of IP profile object
+            # provider-network:
+            #     physical-network: <update> Name of the physical network on which provider network is built
+            #     segmentation_id: <update> ID of segregated virtual network
+            {%- endif %}
+
+            # Specify the constituent VNFs
+            # member-vnf-index-ref - entry from constituent vnf
+            # vnfd-id-ref - VNFD id
+            # vnfd-connection-point-ref - connection point name in the VNFD
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: "1"
+                vnfd-id-ref: {{ name }}_vnfd
+                # NOTE: Validate the entry below
+                vnfd-connection-point-ref: vnf-cp0
+                {%- if detailed is sameas true %}
+                # ip-address: <update> IP address of the connection point
+                {%- endif %}
+
+            {%- for x in range(1, interfaces + 1 ) %}
+            -   member-vnf-index-ref: "1"
+                vnfd-id-ref: {{ name }}_vnfd
+                # NOTE: Validate the entry below
+                vnfd-connection-point-ref: vnf-cp{{ x }}
+                {%- if detailed is sameas true %}
+                # ip-address: <update> IP address of the connection point
+                {%- endif %}
+            {%- endfor %}
+
+            {%- if detailed is sameas true %}
+            # List of IP Profiles.
+            # IP Profile describes the IP characteristics for the Virtual-Link
+            #ip-profiles
+            #-   name: <update>
+            #    description: <update>
+            #    ip-profile-params:
+            #    -   ip-version:
+            #        subnet-address: <update>
+            #        gateway-address: <update>
+            #        security-group: <update>
+            #        subnet-prefix-pool: <update>
+            #        dns-server:
+            #        -   address: <update>
+            #        dhcp-params:
+            #        -   enabled: <update>
+            #            start-address: <update>
+            #            count: <update>
+
+            # Information about NS configuration
+            #ns-configuration:
+            #    juju:
+            #        charm: <update>
+            #        proxy: <update>
+            #        vca-relationships:
+            #            relation:
+            #            -   requires: <update>
+            #                provides: <update>
+
+            #    # List of config primitives supported by the
+            #    # configuration agent for this NS.
+            #    config-primitive:
+            #    -   name: <update>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update>
+            #            mandatory: <update>
+            #            default-value: <update>
+            #            parameter-pool: <update>
+            #            read-only: <update>
+            #            hidden: <update>
+
+            #    # Initial set of configuration primitives.
+            #    initial-config-primitive:
+            #    -   seq: <update>
+            #        name: <update>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update>
+            #            value: <update>
+
+            #    # Terminate set of configuration primitives.
+            #    terminate-config-primitive:
+            #    -   seq: <update>
+            #        name: <update>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update>
+            #            value: <update>
+
+            #    # List of VCA related metric
+            #    metrics:
+            #    -   name: <update>
+
+            # Used to configure the list of public keys to be injected as part
+            # of ns instantiation
+            #key-pair:
+            #-   name: <update>
+            #    key: <update>
+
+            # List of users to be added through cloud-config
+            #user:
+            #-   name: <update>
+            #    user-info: <update>
+            #    key-pair:
+            #    -   name: <update>
+            #        key: <update>
+
+            # List of VNF Forwarding Graph Descriptors (VNFFGD)
+            #vnffgd:
+            #-   id: <update>
+            #    name: <update>
+            #    short-name: <update>
+            #    vendor: <update>
+            #    description: <update>
+            #    version: <update>
+            #    # List of Rendered Service Paths
+            #    rsp:
+            #    -   id: <update>
+            #        name: <update>
+            #        vnfd-connection-point-ref:
+            #        -   member-vnf-index-ref: <update>
+            #            order: <update>
+            #            vnfd-id-ref: <update>
+            #            vnfd-ingress-connection-point-ref: <update>
+            #            vnfd-egress-connection-point-ref: <update>
+            #    # List of classifier rules
+            #    classifier:
+            #    -   id: <update>
+            #        name: <update>
+            #        rsp-id-ref: <update>
+            #        member-vnf-index-ref: <update>
+            #        vnfd-id-ref: <update>
+            #        vnfd-connection-point-ref: <update>
+            #        match-attributes:
+            #        -   id: <update>
+            #            ip-proto: <update>
+            #            source-ip-address: <update>
+            #            destination-ip-address: <update>
+            #            source-port: <update>
+            #            destination-port: <update>
+            {%- endif %}
diff --git a/osmclient/templates/nst.yaml.j2 b/osmclient/templates/nst.yaml.j2
new file mode 100644 (file)
index 0000000..87093ab
--- /dev/null
@@ -0,0 +1,125 @@
+# Copyright 2019 ETSI OSM
+#
+# All Rights Reserved.
+#
+#    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.
+nst:
+-   id: {{ name }}_nst
+    name: {{ name }}_nst
+
+    # Slice Service Type [eMBB, mMTC, URLLC]
+    SNSSAI-identifier:
+        slice-service-type: eMBB
+        {%- if detailed is sameas true %}
+        #slice-differentiator: <update, optional>
+        {%- endif %}
+
+    # Quality of service identifier
+    quality-of-service:
+        id: 1
+        {%- if detailed is sameas true %}
+        #resource-type: <update, optional>
+        #priority-level: <update, optional>
+        #packet-delay-budget: <update, optional>
+        #packet-error-rate: <update, optional>
+        #default-max-data-burst: <update, optional>
+        {%- endif %}
+
+    # Netslice subnets (Network services)
+    netslice-subnet:
+    {%- for x in range(1, netslice_subnets + 1 ) %}
+    -   id: {{ name }}_nsd_{{ x }}
+        is-shared-nss: 'false'
+        description: NetSlice Subnet (service) {{ name }}_nsd_{{ x }}
+        nsd-ref: {{ name }}_nsd
+    {%- endfor %}
+
+    # Netslice virtual links
+    netslice-vld:
+    # Additional netslice-vld can be created by copying the
+    # vld descriptor below
+    -   id: {{ name }}_slice_vld_mgmt
+        name: {{ name }}_slice_vld_mgmt
+        type: ELAN
+        mgmt-network: 'true'
+        {%- if detailed is sameas true %}
+        #short-name: <update, optional>
+        #description: <update, optional>
+        {%- endif %}
+        nss-connection-point-ref:
+        # Specify the connection points
+        # Multiple connection points can be defined
+        -   nss-ref: {{ name }}_nsd_1
+            nsd-connection-point-ref: {{ name }}_nsd_cp_mgmt
+            {%- if detailed is sameas true %}
+            #ip-address: <update>
+            {%- endif %}
+
+    {%- for x in range(1, netslice_vlds + 1 ) %}
+    -   id: {{ name }}_slice_vld_data{{ x }}
+        name: {{ name }}_slice_vld_data{{ x }}
+        type: ELAN
+        {%- if detailed is sameas true %}
+        #mgmt-network: <update>
+        #short-name: <update, optional>
+        #description: <update, optional>
+        #type: <update, optional>
+        {%- endif %}
+        nss-connection-point-ref:
+        # Specify the connection points
+        # Multiple connection points can be defined
+        -   nss-ref: {{ name }}_nsd
+            nsd-connection-point-ref: {{ name }}_nsd_cp_data{{ x }}
+            {%- if detailed is sameas true %}
+            #ip-address: <update>
+            {%- endif %}
+    {%- endfor %}
+
+    {%- if detailed is sameas true %}
+    #netslice-connection-point:
+    #-   name:
+    #    floating-ip-required: <update>
+    #    # Choice connection | netslice-vld-ref / nsd-connection-point-ref
+    #    netslice-vld-id-ref: <update, optional>
+    #    #
+    #    nsd-id-ref: <update, optional>
+    #    nsd-connection-point-ref: <update, optional>
+
+    #netslicefgd:
+    #-   id: <update>
+    #    name: <update, optional>
+    #    short-name: <update, optional>
+    #    vendor: <update, optional>
+    #    description: <update, optional>
+    #    version: <update, optional>
+    #    rsp:
+    #    -   id: <update>
+    #        name: <update, optional>
+    #        nsd-connection-point-ref:
+    #        -   nsd-ref: <update>
+    #            order: <update, optional>
+    #            nsd-connection-point-ref: <update, optional>
+    #    classifier:
+    #    -   id: <update>
+    #        name: <update, optional>
+    #        rsp-id-ref: <update, optional>
+    #        match-attributes:
+    #        -   id: <update>
+    #            ip-proto: <update, optional>
+    #            source-ip-address: <update, optional>
+    #            destination-ip-address: <update, optional>
+    #            source-port: <update, optional>
+    #            destination-port: <update, optional>
+    #        nsd-ref: <update, optional>
+    #        nsd-connection-point-ref: <update, optional>
+    {%- endif %}
diff --git a/osmclient/templates/vnfd.yaml.j2 b/osmclient/templates/vnfd.yaml.j2
new file mode 100644 (file)
index 0000000..0dfc1f2
--- /dev/null
@@ -0,0 +1,334 @@
+# Copyright 2019 ETSI OSM
+#
+# All Rights Reserved.
+#
+#    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.
+
+vnfd-catalog:
+    schema-version: "v3.0"
+    vnfd:
+    -   id: {{ name }}_vnfd
+        name: {{ name }}_vnfd
+        short-name: {{ name }}_vnfd
+        description: Generated by OSM package generator
+        vendor: OSM
+        version: '1.0'
+
+        {%- if detailed is sameas true %}
+        #Place the logo as png in icons directory and provide the name here
+        #logo: <update, optional>
+
+        {%- endif %}
+
+        # Management interface
+        mgmt-interface:
+            cp: vnf-cp0
+
+        # At least one VDU need to be specified
+        vdu:
+        # Additional VDUs can be created by copying the
+        # VDU descriptor below
+        -   id: {{ name }}_vnfd-VM
+            name: {{ name }}_vnfd-VM
+            description: {{ name }}_vnfd-VM
+            count: 1
+            {%- if detailed is sameas true %}
+            #pdu-type: <update, optional>
+            #mgmt-vpci: <update, optional>
+            {%- endif %}
+
+            # Flavour of the VM to be instantiated for the VDU
+            vm-flavor:
+                vcpu-count: {{ vcpu }}
+                memory-mb: {{ memory }}
+                storage-gb: {{ storage }}
+
+            # Image including the full path
+            image: "{{ image }}"
+            {%- if detailed is sameas true %}
+            #image-checksum: <update, optional>
+            {%- endif %}
+
+            interface:
+            # Specify the external interfaces
+            # There can be multiple interfaces defined
+            -   name: eth0
+                type: EXTERNAL
+                virtual-interface:
+                    type: PARAVIRT
+                    {%- if detailed is sameas true %}
+                    #vpci: <update, optional>
+                    #bandwidth: <update, optional>
+                    {%- endif %}
+                external-connection-point-ref: vnf-cp0
+                {%- if detailed is sameas true %}
+                #internal-connection-point-ref: <update, optional>
+                #mac-address <update, optional>
+                #mgmt-interface: <update, optional>
+                {%- endif %}
+
+            {%- for x in range(1, interfaces + 1 ) %}
+            -   name: eth{{ x }}
+                type: EXTERNAL
+                virtual-interface:
+                    type: PARAVIRT
+                    {%- if detailed is sameas true %}
+                    #vpci: <update, optional>
+                    #bandwidth: <update, optional>
+                    {%- endif %}
+                external-connection-point-ref: vnf-cp{{ x }}
+                {%- if detailed is sameas true %}
+                #internal-connection-point-ref: <update, optional>
+                #mac-address <update, optional>
+                #mgmt-interface: <update, optional>
+                {%- endif %}
+            {%- endfor %}
+
+            {%- if detailed is sameas true %}
+            #guest-epa:
+            #    mempage-size: <update, optional>
+            #    # Choice cpu-pinning / cpu-quota
+            #    cpu-pinning-policy: <update, optional> [DEDICATED, SHARED, ANY]
+            #    cpu-thread-pinning-policy: <update, optional> [AVOID, SEPARATED, ISOLATE, PREFER]
+            #    cpu-quota:
+            #       limit: <update, optional>
+            #       reserve: <update, optional>
+            #       shares: <update, optional>
+            #    mem-quota:
+            #       limit: <update, optional>
+            #       reserve: <update, optional>
+            #       shares: <update, optional>
+            #    disk-io-quota:
+            #       limit: <update, optional>
+            #       reserve: <update, optional>
+            #       shares: <update, optional>
+            #    vif-quota:
+            #       limit: <update, optional>
+            #       reserve: <update, optional>
+            #       shares: <update, optional>
+
+            #alarm:
+            #-   alarm-id: <update>
+            #    vnf-monitoring-param-ref: <update, optional>
+            #    operation: <update, optional>
+            #    value: <update, optional>
+            #    actions:
+            #        ok:
+            #        -   url: <update>
+            #        insufficient-data:
+            #        -   url: <update>
+            #        alarm:
+            #        -   url: <update>
+            #
+            #alternative-images:
+            #-   vim-type: <update>
+            #    image: <update, optional>
+            #    image-checksum: <update, optional>
+            #
+            #vdu-configuration:
+            #    # Configure the VNF or VDU through Juju.
+            #    juju:
+            #        charm: <update, optional>
+            #        proxy: <update, optional>
+            #        vca-relationships:
+            #            relation:
+            #            -   requires: <update>
+            #                provides: <update>
+            #    config-primitive:
+            #    -   name: <update>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update, optional>
+            #            mandatory: <update, optional>
+            #            default-value: <update, optional>
+            #            parameter-pool: <update, optional>
+            #            read-only: <update, optional>
+            #            hidden: <update, optional>
+            #    initial-config-primitive:
+            #    -   seq: <update>
+            #        name: <update, optional>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update, optional>
+            #            value: <update, optional>
+            #    terminate-config-primitive:
+            #    -   seq: <update>
+            #        name: <update, optional>
+            #        parameter:
+            #        -   name: <update>
+            #            data-type: <update, optional>
+            #            value: <update, optional>
+            #    metrics:
+            #    -   name: <update>
+            #    config-access:
+            #        ssh-access:
+            #            required: <update, optional>
+            #            default-user: <update, optional>
+            #
+            #monitoring-param:
+            #-   id: <update>
+            #    nfvi-metric: <update, optional>
+            #    interface-name-ref: <update, optional>
+            #
+            ## Choice cloud-init / cloud-init-file
+            #cloud-init: <update, optional>
+            #cloud-init-file: <update, optional>
+            #supplemental-boot-data:
+            #    boot-data-drive: <update, optional>
+            #
+            #internal-connection-point:
+            #-   id: <update>
+            #    name: <update, optional>
+            #    short-name: <update, optional>
+            #    type: <update, optional>
+            #    port-security-enabled: <update, optional>
+            #    internal-vld-ref: <update, optional>
+            #
+            #interface:
+            #-   name: <update>
+            #    position: <update, optional>
+            #    mgmt-interface: <update, optional>
+            #    type: <update, optional>
+            #    mac-address: <update, optional>
+            #    # Choice: connection-point-type | internal-connection-point-ref / external-connection-point-ref
+            #    internal-connection-point-ref: <update, optional>
+            #    external-connection-point-ref: <update, optional>
+            #    virtual-interface:
+            #        type: <update, optional> # PARAVIRT,OM-MGMT,PCI-PASSTHROUGH,SR-IOV,VIRTIO,E1000,RTL8139,PCNET
+            #        vpci: <update, optional>
+            #        bandwidth: <update, optional>
+            #
+            #volumes:
+            #-   name: <update>
+            #    description: <update, optional>
+            #    size: <update, optional>
+            #    device-bus: <update, optional> # ide, usb, virtio, iscsi
+            #    device-type: <update, optional> # disk, cdrom, floopy, lun
+            #    # Choice volume-source | ephemeral / image
+            #    ephemeral: <update, optional>
+            #    image: <update, optional>
+            #    image-checksum: <update, optional>
+
+        #scaling-group-descriptor:
+        #-   name: <update>
+        #    min-instance-count: <update, optional>
+        #    max-instance-count: <update, optional>
+        #    scaling-policy:
+        #    -   name: <update>
+        #        scaling-type: <update, optional>
+        #        enabled: <update, optional>
+        #        scale-in-operator-type: <update, optional>
+        #        scale-out-operator-type: <update, optional>
+        #        threshold-time: <update, optional>
+        #        cooldown-time: <update, optional>
+        #        scaling-criteria: <update, optional>
+        #        -   name: <update>
+        #            scale-in-threshold: <update, optional>
+        #            scale-in-relational-operation: <update, optional>
+        #            scale-out-threshold: <update, optional>
+        #            scale-out-relational-operation: <update, optional>
+        #            vnf-monitoring-param-ref: <update, optional>
+        #    vdu:
+        #    -   vdu-id-ref: <update>
+        #        count: <update, optional>
+        #    scaling-config-action:
+        #    -   trigger: <update>
+        #        vnf-config-primitive-name-ref: <update, optional>
+
+        # List of monitoring params at NS level
+        #monitoring-param:
+        #-   id: <update>
+        #    name: <update, optional>
+        #    aggregation-type: <update, optional>
+        #    # Choice monitoring-type | vdu-monitoring-param / vnf-metric / vdu-metric
+        #    vdu-monitoring-param:
+        #        vdu-ref: <update, optional>
+        #        vdu-monitoring-param-ref: <update, optional>
+        #    vnf-metric:
+        #        vnfm-metric-name-ref: <update, optional>
+        #    vdu-metric:
+        #        vdu-ref: <update, optional>
+        #        vdu-metric-name-ref: <update, optional>
+        #
+        # Placement groups at VNF Level
+        #placement-groups:
+        #-   name: <update>
+        #    requirement: <update, optional>
+        #    strategy: <update, optional> # COLOCATION, ISOLATION
+        #    member-vdus:
+        #    -   member-vdu-ref: <update>
+        {%- endif %}
+        {%- if vdus > 1 %}
+        {% for y in range(1, vdus ) %}
+        -   id: {{ name }}_vnfd{{y}}-VM
+            name: {{ name }}_vnfd{{y}}-VM
+            description: {{ name }}_vnfd{{y}}-VM
+            count: 1
+            {%- if detailed is sameas true %}
+            #pdu-type: <update, optional>
+            #mgmt-vpci: <update, optional>
+            {%- endif %}
+
+            # Flavour of the VM to be instantiated for the VDU
+            vm-flavor:
+                vcpu-count: {{ vcpu }}
+                memory-mb: {{ memory }}
+                storage-gb: {{ storage }}
+
+            # Image including the full path
+            image: "{{ image }}"
+            {%- if detailed is sameas true %}
+            #image-checksum: <update, optional>
+            {%- endif %}
+
+            interface:
+            # Specify the external interfaces
+            # There can be multiple interfaces defined
+            -   name: eth0
+                type: EXTERNAL
+                virtual-interface:
+                    type: PARAVIRT
+                    {%- if detailed is sameas true %}
+                    #vpci: <update, optional>
+                    #bandwidth: <update, optional>
+                    {%- endif %}
+                external-connection-point-ref: vnf-cp0
+                {%- if detailed is sameas true %}
+                #internal-connection-point-ref: <update, optional>
+                #mac-address <update, optional>
+                #mgmt-interface: <update, optional>
+                {%- endif %}
+
+            {%- for z in range(1, interfaces + 1 ) %}
+            -   name: eth{{ z }}
+                type: EXTERNAL
+                virtual-interface:
+                    type: PARAVIRT
+                    {%- if detailed is sameas true %}
+                    #vpci: <update, optional>
+                    #bandwidth: <update, optional>
+                    {%- endif %}
+                external-connection-point-ref: vnf-cp{{ z }}
+                {%- if detailed is sameas true %}
+                #internal-connection-point-ref: <update, optional>
+                #mac-address <update, optional>
+                #mgmt-interface: <update, optional>
+                {%- endif %}
+            {%- endfor %}
+        {%- endfor %}
+        {%- endif %}
+        connection-point:
+        -   name: vnf-cp0
+        {%- for x in range(1, interfaces + 1 ) %}
+        -   name: vnf-cp{{ x }}
+        {%- endfor %}
\ No newline at end of file
index b7ce83b..ba765e1 100644 (file)
@@ -27,6 +27,7 @@ from osmclient.v1 import package
 from osmclient.v1 import vca
 from osmclient.v1 import utils
 from osmclient.common import http
+from osmclient.common import package_tool
 
 
 class Client(object):
@@ -102,6 +103,7 @@ class Client(object):
             **kwargs)
         self.vca = vca.Vca(http_client, client=self, **kwargs)
         self.utils = utils.Utils(http_client, **kwargs)
+        self.package_tool = package_tool.PackageTool(client=self)
 
     @property
     def so_rbac_project_path(self):
index 32a91a2..d5c9a2b 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,6 @@
 #
+# All Rights Reserved.
+#
 #    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
@@ -10,7 +12,6 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-#
 from setuptools import setup, find_packages
 
 setup(
@@ -22,7 +23,11 @@ setup(
     packages=find_packages(),
     include_package_data=True,
     install_requires=[
-        'Click', 'prettytable', 'pyyaml', 'pycurl', 'python-magic'
+        'Click', 'prettytable', 'pyyaml', 'pycurl', 'python-magic',
+        'jinja2', 'osm-im'
+    ],
+    dependency_links=[
+        'git+https://osm.etsi.org/gerrit/osm/IM.git#egg=osm-im',
     ],
     setup_requires=['setuptools-version-command'],
     test_suite='nose.collector',
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644 (file)
index 0000000..301fb43
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2019 ETSI OSM
+#
+# All Rights Reserved.
+#
+#    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.
+
+nose
+mock
+git+https://osm.etsi.org/gerrit/osm/IM.git#egg=osm-im
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
index f6ec0c7..4f7d816 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -1,3 +1,6 @@
+# Copyright 2019 ETSI OSM
+#
+# All Rights Reserved.
 #
 #    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
 #
 [tox]
 envlist = py27,py35,flake8,pyflakes
-toxworkdir={homedir}/.tox
+toxworkdir={toxinidir}/.tox
 
 [testenv]
-deps = nose
-       mock
-commands =
-    nosetests
+deps = -r{toxinidir}/test-requirements.txt
+commands=nosetests
+install_command = python -m pip install -r test-requirements.txt -U {opts} {packages}
 
 [testenv:flake8]
-basepython = python
+basepython = python3
 deps = flake8
 commands =
     flake8 setup.py