From: Felipe Vicens Date: Fri, 25 Oct 2019 14:42:41 +0000 (+0200) Subject: Feature 8047: osmclient package creation and validation tool X-Git-Tag: v7.0.0rc1~9 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F92%2F8092%2F31;p=osm%2Fosmclient.git Feature 8047: osmclient package creation and validation tool Change-Id: Ie1e0be7f5029a4b323fa585f8aa39631568176ac Signed-off-by: Felipe Vicens --- diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4deac18 --- /dev/null +++ b/MANIFEST.in @@ -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 index 0000000..7cc10be --- /dev/null +++ b/osmclient/common/package_tool.py @@ -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 diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index ee102e6..b1df0a5 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -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() diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py index a4f952c..6281ffe 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -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 index 0000000..0e0c7a3 --- /dev/null +++ b/osmclient/templates/nsd.yaml.j2 @@ -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: 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: + # vim-network-name: Name of network in VIM account + # ip-profile-ref: Name of reference of IP profile object + # provider-network: + # physical-network: Name of the physical network on which provider network is built + # segmentation_id: 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: 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: 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: + # description: + # ip-profile-params: + # - ip-version: + # subnet-address: + # gateway-address: + # security-group: + # subnet-prefix-pool: + # dns-server: + # - address: + # dhcp-params: + # - enabled: + # start-address: + # count: + + # Information about NS configuration + #ns-configuration: + # juju: + # charm: + # proxy: + # vca-relationships: + # relation: + # - requires: + # provides: + + # # List of config primitives supported by the + # # configuration agent for this NS. + # config-primitive: + # - name: + # parameter: + # - name: + # data-type: + # mandatory: + # default-value: + # parameter-pool: + # read-only: + # hidden: + + # # Initial set of configuration primitives. + # initial-config-primitive: + # - seq: + # name: + # parameter: + # - name: + # data-type: + # value: + + # # Terminate set of configuration primitives. + # terminate-config-primitive: + # - seq: + # name: + # parameter: + # - name: + # data-type: + # value: + + # # List of VCA related metric + # metrics: + # - name: + + # Used to configure the list of public keys to be injected as part + # of ns instantiation + #key-pair: + #- name: + # key: + + # List of users to be added through cloud-config + #user: + #- name: + # user-info: + # key-pair: + # - name: + # key: + + # List of VNF Forwarding Graph Descriptors (VNFFGD) + #vnffgd: + #- id: + # name: + # short-name: + # vendor: + # description: + # version: + # # List of Rendered Service Paths + # rsp: + # - id: + # name: + # vnfd-connection-point-ref: + # - member-vnf-index-ref: + # order: + # vnfd-id-ref: + # vnfd-ingress-connection-point-ref: + # vnfd-egress-connection-point-ref: + # # List of classifier rules + # classifier: + # - id: + # name: + # rsp-id-ref: + # member-vnf-index-ref: + # vnfd-id-ref: + # vnfd-connection-point-ref: + # match-attributes: + # - id: + # ip-proto: + # source-ip-address: + # destination-ip-address: + # source-port: + # destination-port: + {%- endif %} diff --git a/osmclient/templates/nst.yaml.j2 b/osmclient/templates/nst.yaml.j2 new file mode 100644 index 0000000..87093ab --- /dev/null +++ b/osmclient/templates/nst.yaml.j2 @@ -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: + {%- endif %} + + # Quality of service identifier + quality-of-service: + id: 1 + {%- if detailed is sameas true %} + #resource-type: + #priority-level: + #packet-delay-budget: + #packet-error-rate: + #default-max-data-burst: + {%- 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: + #description: + {%- 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: + {%- 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: + #short-name: + #description: + #type: + {%- 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: + {%- endif %} + {%- endfor %} + + {%- if detailed is sameas true %} + #netslice-connection-point: + #- name: + # floating-ip-required: + # # Choice connection | netslice-vld-ref / nsd-connection-point-ref + # netslice-vld-id-ref: + # # + # nsd-id-ref: + # nsd-connection-point-ref: + + #netslicefgd: + #- id: + # name: + # short-name: + # vendor: + # description: + # version: + # rsp: + # - id: + # name: + # nsd-connection-point-ref: + # - nsd-ref: + # order: + # nsd-connection-point-ref: + # classifier: + # - id: + # name: + # rsp-id-ref: + # match-attributes: + # - id: + # ip-proto: + # source-ip-address: + # destination-ip-address: + # source-port: + # destination-port: + # nsd-ref: + # nsd-connection-point-ref: + {%- endif %} diff --git a/osmclient/templates/vnfd.yaml.j2 b/osmclient/templates/vnfd.yaml.j2 new file mode 100644 index 0000000..0dfc1f2 --- /dev/null +++ b/osmclient/templates/vnfd.yaml.j2 @@ -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: + + {%- 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: + #mgmt-vpci: + {%- 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: + {%- 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: + #bandwidth: + {%- endif %} + external-connection-point-ref: vnf-cp0 + {%- if detailed is sameas true %} + #internal-connection-point-ref: + #mac-address + #mgmt-interface: + {%- endif %} + + {%- for x in range(1, interfaces + 1 ) %} + - name: eth{{ x }} + type: EXTERNAL + virtual-interface: + type: PARAVIRT + {%- if detailed is sameas true %} + #vpci: + #bandwidth: + {%- endif %} + external-connection-point-ref: vnf-cp{{ x }} + {%- if detailed is sameas true %} + #internal-connection-point-ref: + #mac-address + #mgmt-interface: + {%- endif %} + {%- endfor %} + + {%- if detailed is sameas true %} + #guest-epa: + # mempage-size: + # # Choice cpu-pinning / cpu-quota + # cpu-pinning-policy: [DEDICATED, SHARED, ANY] + # cpu-thread-pinning-policy: [AVOID, SEPARATED, ISOLATE, PREFER] + # cpu-quota: + # limit: + # reserve: + # shares: + # mem-quota: + # limit: + # reserve: + # shares: + # disk-io-quota: + # limit: + # reserve: + # shares: + # vif-quota: + # limit: + # reserve: + # shares: + + #alarm: + #- alarm-id: + # vnf-monitoring-param-ref: + # operation: + # value: + # actions: + # ok: + # - url: + # insufficient-data: + # - url: + # alarm: + # - url: + # + #alternative-images: + #- vim-type: + # image: + # image-checksum: + # + #vdu-configuration: + # # Configure the VNF or VDU through Juju. + # juju: + # charm: + # proxy: + # vca-relationships: + # relation: + # - requires: + # provides: + # config-primitive: + # - name: + # parameter: + # - name: + # data-type: + # mandatory: + # default-value: + # parameter-pool: + # read-only: + # hidden: + # initial-config-primitive: + # - seq: + # name: + # parameter: + # - name: + # data-type: + # value: + # terminate-config-primitive: + # - seq: + # name: + # parameter: + # - name: + # data-type: + # value: + # metrics: + # - name: + # config-access: + # ssh-access: + # required: + # default-user: + # + #monitoring-param: + #- id: + # nfvi-metric: + # interface-name-ref: + # + ## Choice cloud-init / cloud-init-file + #cloud-init: + #cloud-init-file: + #supplemental-boot-data: + # boot-data-drive: + # + #internal-connection-point: + #- id: + # name: + # short-name: + # type: + # port-security-enabled: + # internal-vld-ref: + # + #interface: + #- name: + # position: + # mgmt-interface: + # type: + # mac-address: + # # Choice: connection-point-type | internal-connection-point-ref / external-connection-point-ref + # internal-connection-point-ref: + # external-connection-point-ref: + # virtual-interface: + # type: # PARAVIRT,OM-MGMT,PCI-PASSTHROUGH,SR-IOV,VIRTIO,E1000,RTL8139,PCNET + # vpci: + # bandwidth: + # + #volumes: + #- name: + # description: + # size: + # device-bus: # ide, usb, virtio, iscsi + # device-type: # disk, cdrom, floopy, lun + # # Choice volume-source | ephemeral / image + # ephemeral: + # image: + # image-checksum: + + #scaling-group-descriptor: + #- name: + # min-instance-count: + # max-instance-count: + # scaling-policy: + # - name: + # scaling-type: + # enabled: + # scale-in-operator-type: + # scale-out-operator-type: + # threshold-time: + # cooldown-time: + # scaling-criteria: + # - name: + # scale-in-threshold: + # scale-in-relational-operation: + # scale-out-threshold: + # scale-out-relational-operation: + # vnf-monitoring-param-ref: + # vdu: + # - vdu-id-ref: + # count: + # scaling-config-action: + # - trigger: + # vnf-config-primitive-name-ref: + + # List of monitoring params at NS level + #monitoring-param: + #- id: + # name: + # aggregation-type: + # # Choice monitoring-type | vdu-monitoring-param / vnf-metric / vdu-metric + # vdu-monitoring-param: + # vdu-ref: + # vdu-monitoring-param-ref: + # vnf-metric: + # vnfm-metric-name-ref: + # vdu-metric: + # vdu-ref: + # vdu-metric-name-ref: + # + # Placement groups at VNF Level + #placement-groups: + #- name: + # requirement: + # strategy: # COLOCATION, ISOLATION + # member-vdus: + # - member-vdu-ref: + {%- 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: + #mgmt-vpci: + {%- 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: + {%- 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: + #bandwidth: + {%- endif %} + external-connection-point-ref: vnf-cp0 + {%- if detailed is sameas true %} + #internal-connection-point-ref: + #mac-address + #mgmt-interface: + {%- endif %} + + {%- for z in range(1, interfaces + 1 ) %} + - name: eth{{ z }} + type: EXTERNAL + virtual-interface: + type: PARAVIRT + {%- if detailed is sameas true %} + #vpci: + #bandwidth: + {%- endif %} + external-connection-point-ref: vnf-cp{{ z }} + {%- if detailed is sameas true %} + #internal-connection-point-ref: + #mac-address + #mgmt-interface: + {%- 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 diff --git a/osmclient/v1/client.py b/osmclient/v1/client.py index b7ce83b..ba765e1 100644 --- a/osmclient/v1/client.py +++ b/osmclient/v1/client.py @@ -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): diff --git a/setup.py b/setup.py index 32a91a2..d5c9a2b 100644 --- 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 index 0000000..301fb43 --- /dev/null +++ b/test-requirements.txt @@ -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 --- 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 @@ -13,16 +16,15 @@ # [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