From 3f592284d97ed71e8d7bab435586f12623691e47 Mon Sep 17 00:00:00 2001 From: garciadeblas Date: Mon, 29 Oct 2018 13:42:42 +0100 Subject: [PATCH] Support of network slices Change-Id: I57a003b880c67bdeddae6f090da63e6d4747127b Signed-off-by: garciadeblas --- osmclient/scripts/osm.py | 457 ++++++++++++++++++++++++++++++++++++- osmclient/sol005/client.py | 4 + osmclient/sol005/ns.py | 1 - osmclient/sol005/nsi.py | 302 ++++++++++++++++++++++++ osmclient/sol005/nst.py | 179 +++++++++++++++ 5 files changed, 938 insertions(+), 5 deletions(-) create mode 100644 osmclient/sol005/nsi.py create mode 100644 osmclient/sol005/nst.py diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index c07b62d..94a503e 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -410,6 +410,128 @@ def ns_op_list(ctx, name): table.align = 'l' print(table) + +def nsi_list(ctx, filter): + '''list all Network Slice Instances''' + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nsi.list(filter) + except ClientException as inst: + print((inst.message)) + exit(1) + table = PrettyTable( + ['netslice instance name', + 'id', + 'operational status', + 'config status', + 'detailed status']) + for nsi in resp: + nsi_name = nsi['name'] + nsi_id = nsi['_id'] + opstatus = nsi['operational-status'] if 'operational-status' in nsi else 'Not found' + configstatus = nsi['config-status'] if 'config-status' in nsi else 'Not found' + detailed_status = nsi['detailed-status'] if 'detailed-status' in nsi else 'Not found' + if configstatus == "config_not_needed": + configstatus = "configured (no charms)" + table.add_row( + [nsi_name, + nsi_id, + opstatus, + configstatus, + detailed_status]) + table.align = 'l' + print(table) + + +@cli.command(name='nsi-list') +@click.option('--filter', default=None, + help='restricts the list to the Network Slice Instances matching the filter') +@click.pass_context +def nsi_list1(ctx, filter): + '''list all Network Slice Instances (NSI)''' + nsi_list(ctx,filter) + + +@cli.command(name='netslice-instance-list') +@click.option('--filter', default=None, + help='restricts the list to the Network Slice Instances matching the filter') +@click.pass_context +def nsi_list2(ctx, filter): + '''list all Network Slice Instances (NSI)''' + nsi_list(ctx,filter) + + +def nst_list(ctx, filter): + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nst.list(filter) + except ClientException as inst: + print((inst.message)) + exit(1) + #print yaml.safe_dump(resp) + table = PrettyTable(['nst name', 'id']) + for nst in resp: + name = nst['name'] if 'name' in nst else '-' + table.add_row([name, nst['_id']]) + table.align = 'l' + print(table) + + +@cli.command(name='nst-list') +@click.option('--filter', default=None, + help='restricts the list to the NST matching the filter') +@click.pass_context +def nst_list1(ctx, filter): + '''list all Network Slice Templates (NST) in the system''' + nst_list(ctx,filter) + + +@cli.command(name='netslice-template-list') +@click.option('--filter', default=None, + help='restricts the list to the NST matching the filter') +@click.pass_context +def nst_list2(ctx, filter): + '''list all Network Slice Templates (NST) in the system''' + nst_list(ctx,filter) + + +def nsi_op_list(ctx, name): + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nsi.list_op(name) + except ClientException as inst: + print((inst.message)) + exit(1) + table = PrettyTable(['id', 'operation', 'status']) + for op in resp: + table.add_row([op['id'], op['lcmOperationType'], + op['operationState']]) + table.align = 'l' + print(table) + + +@cli.command(name='nsi-op-list') +@click.argument('name') +@click.pass_context +def nsi_op_list1(ctx, name): + '''shows the history of operations over a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + ''' + nsi_op_list(ctx,name) + + +@cli.command(name='netslice-instance-op-list') +@click.argument('name') +@click.pass_context +def nsi_op_list2(ctx, name): + '''shows the history of operations over a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + ''' + nsi_op_list(ctx,name) + + #################### # SHOW operations #################### @@ -617,6 +739,7 @@ def ns_monitoring_show(ctx, ns_name): table.align = 'l' print(table) + @cli.command(name='ns-op-show', short_help='shows the info of an operation') @click.argument('id') @click.option('--filter', default=None) @@ -641,6 +764,142 @@ def ns_op_show(ctx, id, filter): print(table) +def nst_show(ctx, name, literal): + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nst.get(name) + #resp = ctx.obj.nst.get_individual(name) + except ClientException as inst: + print((inst.message)) + exit(1) + + if literal: + print(yaml.safe_dump(resp)) + return + + table = PrettyTable(['field', 'value']) + for k, v in list(resp.items()): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = 'l' + print(table) + + +@cli.command(name='nst-show', short_help='shows the content of a Network Slice Template (NST)') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.argument('name') +@click.pass_context +def nst_show1(ctx, name, literal): + '''shows the content of a Network Slice Template (NST) + + NAME: name or ID of the NST + ''' + nst_show(ctx, name, literal) + + +@cli.command(name='netslice-template-show', short_help='shows the content of a Network Slice Template (NST)') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.argument('name') +@click.pass_context +def nst_show2(ctx, name, literal): + '''shows the content of a Network Slice Template (NST) + + NAME: name or ID of the NST + ''' + nst_show(ctx, name, literal) + + +def nsi_show(ctx, name, literal, filter): + try: + check_client_version(ctx.obj, ctx.command.name) + nsi = ctx.obj.nsi.get(name) + except ClientException as inst: + print((inst.message)) + exit(1) + + if literal: + print(yaml.safe_dump(nsi)) + return + + table = PrettyTable(['field', 'value']) + + for k, v in list(nsi.items()): + if filter is None or filter in k: + table.add_row([k, json.dumps(v, indent=2)]) + + table.align = 'l' + print(table) + + +@cli.command(name='nsi-show', short_help='shows the content of a Network Slice Instance (NSI)') +@click.argument('name') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.option('--filter', default=None) +@click.pass_context +def nsi_show1(ctx, name, literal, filter): + '''shows the content of a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + ''' + nsi_show(ctx, name, literal, filter) + + +@cli.command(name='netslice-instance-show', short_help='shows the content of a Network Slice Instance (NSI)') +@click.argument('name') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.option('--filter', default=None) +@click.pass_context +def nsi_show2(ctx, name, literal, filter): + '''shows the content of a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + ''' + nsi_show(ctx, name, literal, filter) + + +def nsi_op_show(ctx, id, filter): + try: + check_client_version(ctx.obj, ctx.command.name) + op_info = ctx.obj.nsi.get_op(id) + except ClientException as inst: + print((inst.message)) + exit(1) + + table = PrettyTable(['field', 'value']) + for k, v in list(op_info.items()): + if filter is None or filter in k: + table.add_row([k, json.dumps(v, indent=2)]) + table.align = 'l' + print(table) + + +@cli.command(name='nsi-op-show', short_help='shows the info of an operation over a Network Slice Instance(NSI)') +@click.argument('id') +@click.option('--filter', default=None) +@click.pass_context +def nsi_op_show1(ctx, id, filter): + '''shows the info of an operation over a Network Slice Instance(NSI) + + ID: operation identifier + ''' + nsi_op_show(ctx, id, filter) + + +@cli.command(name='netslice-instance-op-show', short_help='shows the info of an operation over a Network Slice Instance(NSI)') +@click.argument('id') +@click.option('--filter', default=None) +@click.pass_context +def nsi_op_show2(ctx, id, filter): + '''shows the info of an operation over a Network Slice Instance(NSI) + + ID: operation identifier + ''' + nsi_op_show(ctx, id, filter) + + #################### # CREATE operations #################### @@ -715,13 +974,13 @@ def vnfd_create2(ctx, filename, overwrite): vnfd_create(ctx, filename, overwrite) -@cli.command(name='ns-create') +@cli.command(name='ns-create', short_help='creates a new Network Service instance') @click.option('--ns_name', - prompt=True) + prompt=True, help='name of the NS instance') @click.option('--nsd_name', - prompt=True) + prompt=True, help='name of the NS descriptor') @click.option('--vim_account', - prompt=True) + prompt=True, help='default VIM account id or name for the deployment') @click.option('--admin_status', default='ENABLED', help='administration status') @@ -762,6 +1021,94 @@ def ns_create(ctx, exit(1) +def nst_create(ctx, filename, overwrite): + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.create(filename, overwrite) + except ClientException as inst: + print((inst.message)) + exit(1) + + +@cli.command(name='nst-create', short_help='creates a new Network Slice Template (NST)') +@click.argument('filename') +@click.option('--overwrite', default=None, + help='overwrites some fields in NST') +@click.pass_context +def nst_create1(ctx, filename, overwrite): + '''creates a new Network Slice Template (NST) + + FILENAME: NST yaml file or NSTpkg tar.gz file + ''' + nst_create(ctx, filename, overwrite) + + +@cli.command(name='netslice-template-create', short_help='creates a new Network Slice Template (NST)') +@click.argument('filename') +@click.option('--overwrite', default=None, + help='overwrites some fields in NST') +@click.pass_context +def nst_create2(ctx, filename, overwrite): + '''creates a new Network Slice Template (NST) + + FILENAME: NST yaml file or NSTpkg tar.gz file + ''' + nst_create(ctx, filename, overwrite) + + +def nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config): + '''creates a new Network Slice Instance (NSI)''' + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nsi.create(nst_name, nsi_name, config=config, ssh_keys=ssh_keys, + account=vim_account) + except ClientException as inst: + print((inst.message)) + exit(1) + + +@cli.command(name='nsi-create', short_help='creates a new Network Slice Instance') +@click.option('--nsi_name', prompt=True, help='name of the Network Slice Instance') +@click.option('--nst_name', prompt=True, help='name of the Network Slice Template') +@click.option('--vim_account', prompt=True, help='default VIM account id or name for the deployment') +@click.option('--ssh_keys', default=None, + help='comma separated list of keys to inject to vnfs') +@click.option('--config', default=None, + help='Netslice specific yaml configuration:\n' + 'netslice_subnet: [\n' + 'id: TEXT, vim_account: TEXT,\n' + 'vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n' + 'vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]' + '],\n' + 'netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]' + ) +@click.pass_context +def nsi_create1(ctx, nst_name, nsi_name, vim_account, ssh_keys, config): + '''creates a new Network Slice Instance (NSI)''' + nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config) + + +@cli.command(name='netslice-instance-create', short_help='creates a new Network Slice Instance') +@click.option('--nsi_name', prompt=True, help='name of the Network Slice Instance') +@click.option('--nst_name', prompt=True, help='name of the Network Slice Template') +@click.option('--vim_account', prompt=True, help='default VIM account id or name for the deployment') +@click.option('--ssh_keys', default=None, + help='comma separated list of keys to inject to vnfs') +@click.option('--config', default=None, + help='Netslice specific yaml configuration:\n' + 'netslice_subnet: [\n' + 'id: TEXT, vim_account: TEXT,\n' + 'vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n' + 'vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]' + '],\n' + 'netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]' + ) +@click.pass_context +def nsi_create2(ctx, nst_name, nsi_name, vim_account, ssh_keys, config): + '''creates a new Network Slice Instance (NSI)''' + nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config) + + #################### # UPDATE operations #################### @@ -774,6 +1121,7 @@ def nsd_update(ctx, name, content): print((inst.message)) exit(1) + @cli.command(name='nsd-update', short_help='updates a NSD/NSpkg') @click.argument('name') @click.option('--content', default=None, @@ -835,6 +1183,41 @@ def vnfd_update2(ctx, name, content): vnfd_update(ctx, name, content) +def nst_update(ctx, name, content): + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.update(name, content) + except ClientException as inst: + print((inst.message)) + exit(1) + + +@cli.command(name='nst-update', short_help='updates a Network Slice Template (NST)') +@click.argument('name') +@click.option('--content', default=None, + help='filename with the NST/NSTpkg replacing the current one') +@click.pass_context +def nst_update1(ctx, name, content): + '''updates a Network Slice Template (NST) + + NAME: name or ID of the NSD/NSpkg + ''' + nst_update(ctx, name, content) + + +@cli.command(name='netslice-template-update', short_help='updates a Network Slice Template (NST)') +@click.argument('name') +@click.option('--content', default=None, + help='filename with the NST/NSTpkg replacing the current one') +@click.pass_context +def nst_update2(ctx, name, content): + '''updates a Network Slice Template (NST) + + NAME: name or ID of the NSD/NSpkg + ''' + nst_update(ctx, name, content) + + #################### # DELETE operations #################### @@ -931,6 +1314,72 @@ def ns_delete(ctx, name, force): exit(1) +def nst_delete(ctx, name, force): + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.delete(name, force) + except ClientException as inst: + print((inst.message)) + exit(1) + + +@cli.command(name='nst-delete', short_help='deletes a Network Slice Template (NST)') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') +@click.pass_context +def nst_delete1(ctx, name, force): + '''deletes a Network Slice Template (NST) + + NAME: name or ID of the NST/NSTpkg to be deleted + ''' + nst_delete(ctx, name, force) + + +@cli.command(name='netslice-template-delete', short_help='deletes a Network Slice Template (NST)') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') +@click.pass_context +def nst_delete2(ctx, name, force): + '''deletes a Network Slice Template (NST) + + NAME: name or ID of the NST/NSTpkg to be deleted + ''' + nst_delete(ctx, name, force) + + +def nsi_delete(ctx, name, force): + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nsi.delete(name, force) + except ClientException as inst: + print((inst.message)) + exit(1) + + +@cli.command(name='nsi-delete', short_help='deletes a Network Slice Instance (NSI)') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') +@click.pass_context +def nsi_delete1(ctx, name, force): + '''deletes a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice instance to be deleted + ''' + nsi_delete(ctx, name, force) + + +@cli.command(name='netslice-instance-delete', short_help='deletes a Network Slice Instance (NSI)') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') +@click.pass_context +def nsi_delete2(ctx, name, force): + '''deletes a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice instance to be deleted + ''' + nsi_delete(ctx, name, force) + + #################### # VIM operations #################### diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py index 5de4ff0..7583165 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -21,6 +21,8 @@ OSM SOL005 client API #from osmclient.v1 import vca from osmclient.sol005 import vnfd from osmclient.sol005 import nsd +from osmclient.sol005 import nst +from osmclient.sol005 import nsi from osmclient.sol005 import ns from osmclient.sol005 import vnf from osmclient.sol005 import vim @@ -75,8 +77,10 @@ class Client(object): self.vnfd = vnfd.Vnfd(self._http_client, client=self) self.nsd = nsd.Nsd(self._http_client, client=self) + self.nst = nst.Nst(self._http_client, client=self) self.package = package.Package(self._http_client, client=self) self.ns = ns.Ns(self._http_client, client=self) + self.nsi = nsi.Nsi(self._http_client, client=self) self.vim = vim.Vim(self._http_client, client=self) self.sdnc = sdncontroller.SdnController(self._http_client, client=self) self.vnf = vnf.Vnf(self._http_client, client=self) diff --git a/osmclient/sol005/ns.py b/osmclient/sol005/ns.py index bda7c3e..9d1fe95 100644 --- a/osmclient/sol005/ns.py +++ b/osmclient/sol005/ns.py @@ -145,7 +145,6 @@ class Ns(object): for vnf in ns_config["vnf"]: if vnf.get("vim_account"): vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account")) - ns["vnf"] = ns_config["vnf"] #print yaml.safe_dump(ns) diff --git a/osmclient/sol005/nsi.py b/osmclient/sol005/nsi.py new file mode 100644 index 0000000..f6009dd --- /dev/null +++ b/osmclient/sol005/nsi.py @@ -0,0 +1,302 @@ +# Copyright 2018 Telefonica +# +# 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. + +""" +OSM NSI (Network Slice Instance) API handling +""" + +from osmclient.common import utils +from osmclient.common.exceptions import ClientException +from osmclient.common.exceptions import NotFound +import yaml +import json + + +class Nsi(object): + + def __init__(self, http=None, client=None): + self._http = http + self._client = client + self._apiName = '/nsilcm' + self._apiVersion = '/v1' + self._apiResource = '/netslice_instances_content' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + + def list(self, filter=None): + """Returns a list of NSI + """ + filter_string = '' + if filter: + filter_string = '?{}'.format(filter) + resp = self._http.get_cmd('{}{}'.format(self._apiBase,filter_string)) + if resp: + return resp + return list() + + def get(self, name): + """Returns an NSI based on name or id + """ + if utils.validate_uuid4(name): + for nsi in self.list(): + if name == nsi['_id']: + return nsi + else: + for nsi in self.list(): + if name == nsi['name']: + return nsi + raise NotFound("nsi {} not found".format(name)) + + def get_individual(self, name): + nsi_id = name + if not utils.validate_uuid4(name): + for nsi in self.list(): + if name == nsi['name']: + nsi_id = nsi['_id'] + break + resp = self._http.get_cmd('{}/{}'.format(self._apiBase, nsi_id)) + #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, nsi_id)) + #print yaml.safe_dump(resp) + if resp: + return resp + raise NotFound("nsi {} not found".format(name)) + + def delete(self, name, force=False): + nsi = self.get(name) + querystring = '' + if force: + querystring = '?FORCE=True' + http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase, + nsi['_id'], querystring)) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code == 202: + print('Deletion in progress') + elif http_code == 204: + print('Deleted') + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException("failed to delete nsi {} - {}".format(name, msg)) + + def create(self, nst_name, nsi_name, account, config=None, + ssh_keys=None, description='default description', + admin_status='ENABLED'): + + nst = self._client.nst.get(nst_name) + + vim_account_id = {} + + def get_vim_account_id(vim_account): + if vim_account_id.get(vim_account): + return vim_account_id[vim_account] + + vim = self._client.vim.get(vim_account) + if vim is None: + raise NotFound("cannot find vim account '{}'".format(vim_account)) + vim_account_id[vim_account] = vim['_id'] + return vim['_id'] + + nsi = {} + nsi['nstId'] = nst['_id'] + nsi['nsiName'] = nsi_name + nsi['nsiDescription'] = description + nsi['vimAccountId'] = get_vim_account_id(account) + #nsi['userdata'] = {} + #nsi['userdata']['key1']='value1' + #nsi['userdata']['key2']='value2' + + if ssh_keys is not None: + # ssh_keys is comma separate list + # ssh_keys_format = [] + # for key in ssh_keys.split(','): + # ssh_keys_format.append({'key-pair-ref': key}) + # + # ns['ssh-authorized-key'] = ssh_keys_format + nsi['ssh_keys'] = [] + for pubkeyfile in ssh_keys.split(','): + with open(pubkeyfile, 'r') as f: + nsi['ssh_keys'].append(f.read()) + if config: + nsi_config = yaml.load(config) + if "netslice-vld" in nsi_config: + for vld in nsi_config["netslice-vld"]: + if vld.get("vim-network-name"): + if isinstance(vld["vim-network-name"], dict): + vim_network_name_dict = {} + for vim_account, vim_net in list(vld["vim-network-name"].items()): + vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net + vld["vim-network-name"] = vim_network_name_dict + nsi["netslice-vld"] = nsi_config["netslice-vld"] + if "netslice-subnet" in nsi_config: + for nssubnet in nsi_config["netslice-subnet"]: + if "vld" in nssubnet: + for vld in nssubnet["vld"]: + if vld.get("vim-network-name"): + if isinstance(vld["vim-network-name"], dict): + vim_network_name_dict = {} + for vim_account, vim_net in list(vld["vim-network-name"].items()): + vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net + vld["vim-network-name"] = vim_network_name_dict + if "vnf" in nssubnet: + for vnf in nsi_config["vnf"]: + if vnf.get("vim_account"): + vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account")) + nsi["netslice-subnet"] = nsi_config["netslice-subnet"] + + #print yaml.safe_dump(nsi) + try: + self._apiResource = '/netslice_instances_content' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + headers = self._client._headers + headers['Content-Type'] = 'application/yaml' + http_header = ['{}: {}'.format(key,val) + for (key,val) in list(headers.items())] + self._http.set_http_header(http_header) + http_code, resp = self._http.post_cmd(endpoint=self._apiBase, + postfields_dict=nsi) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + if resp: + resp = json.loads(resp) + if not resp or 'id' not in resp: + raise ClientException('unexpected response from server - {} '.format( + resp)) + print(resp['id']) + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException(msg) + except ClientException as exc: + message="failed to create nsi: {} nst: {}\nerror:\n{}".format( + nsi_name, + nst_name, + exc.message) + raise ClientException(message) + + def list_op(self, name, filter=None): + """Returns the list of operations of a NSI + """ + nsi = self.get(name) + try: + self._apiResource = '/nsi_lcm_op_occs' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + filter_string = '' + if filter: + filter_string = '&{}'.format(filter) + http_code, resp = self._http.get2_cmd('{}?nsiInstanceId={}'.format( + self._apiBase, nsi['_id'], + filter_string) ) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code == 200: + if resp: + resp = json.loads(resp) + return resp + else: + raise ClientException('unexpected response from server') + else: + msg = "" + if resp: + try: + resp = json.loads(resp) + msg = resp['detail'] + except ValueError: + msg = resp + raise ClientException(msg) + except ClientException as exc: + message="failed to get operation list of NSI {}:\nerror:\n{}".format( + name, + exc.message) + raise ClientException(message) + + def get_op(self, operationId): + """Returns the status of an operation + """ + try: + self._apiResource = '/nsi_lcm_op_occs' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + http_code, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, operationId)) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code == 200: + if resp: + resp = json.loads(resp) + return resp + else: + raise ClientException('unexpected response from server') + else: + msg = "" + if resp: + try: + resp = json.loads(resp) + msg = resp['detail'] + except ValueError: + msg = resp + raise ClientException(msg) + except ClientException as exc: + message="failed to get status of operation {}:\nerror:\n{}".format( + operationId, + exc.message) + raise ClientException(message) + + def exec_op(self, name, op_name, op_data=None): + """Executes an operation on a NSI + """ + nsi = self.get(name) + try: + self._apiResource = '/netslice_instances' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + endpoint = '{}/{}/{}'.format(self._apiBase, nsi['_id'], op_name) + #print 'OP_NAME: {}'.format(op_name) + #print 'OP_DATA: {}'.format(json.dumps(op_data)) + http_code, resp = self._http.post_cmd(endpoint=endpoint, postfields_dict=op_data) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + if resp: + resp = json.loads(resp) + if not resp or 'id' not in resp: + raise ClientException('unexpected response from server - {}'.format( + resp)) + print(resp['id']) + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException(msg) + except ClientException as exc: + message="failed to exec operation {}:\nerror:\n{}".format( + name, + exc.message) + raise ClientException(message) + diff --git a/osmclient/sol005/nst.py b/osmclient/sol005/nst.py new file mode 100644 index 0000000..b7c67be --- /dev/null +++ b/osmclient/sol005/nst.py @@ -0,0 +1,179 @@ +# Copyright 2018 Telefonica +# +# 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. + +""" +OSM NST (Network Slice Template) API handling +""" + +from osmclient.common.exceptions import NotFound +from osmclient.common.exceptions import ClientException +from osmclient.common import utils +import json +import magic +#from os import stat +#from os.path import basename + +class Nst(object): + + def __init__(self, http=None, client=None): + self._http = http + self._client = client + self._apiName = '/nst' + self._apiVersion = '/v1' + self._apiResource = '/netslice_templates' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + + def list(self, filter=None): + filter_string = '' + if filter: + filter_string = '?{}'.format(filter) + resp = self._http.get_cmd('{}{}'.format(self._apiBase, filter_string)) + #print yaml.safe_dump(resp) + if resp: + return resp + return list() + + def get(self, name): + if utils.validate_uuid4(name): + for nst in self.list(): + if name == nst['_id']: + return nst + else: + for nst in self.list(): + if 'name' in nst and name == nst['name']: + return nst + raise NotFound("nst {} not found".format(name)) + + def get_individual(self, name): + nst = self.get(name) + # It is redundant, since the previous one already gets the whole nstinfo + # The only difference is that a different primitive is exercised + resp = self._http.get_cmd('{}/{}'.format(self._apiBase, nst['_id'])) + #print yaml.safe_dump(resp) + if resp: + return resp + raise NotFound("nst {} not found".format(name)) + + def get_thing(self, name, thing, filename): + nst = self.get(name) + headers = self._client._headers + headers['Accept'] = 'application/binary' + http_code, resp = self._http.get2_cmd('{}/{}/{}'.format(self._apiBase, nst['_id'], thing)) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + if resp: + #store in a file + return resp + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException("failed to get {} from {} - {}".format(thing, name, msg)) + + def get_descriptor(self, name, filename): + self.get_thing(name, 'nst', filename) + + def get_package(self, name, filename): + self.get_thing(name, 'nst_content', filename) + + def get_artifact(self, name, artifact, filename): + self.get_thing(name, 'artifacts/{}'.format(artifact), filename) + + def delete(self, name, force=False): + nst = self.get(name) + querystring = '' + if force: + querystring = '?FORCE=True' + http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase, + nst['_id'], querystring)) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code == 202: + print('Deletion in progress') + elif http_code == 204: + print('Deleted') + else: + msg = "" + if resp: + try: + resp = json.loads(resp) + except ValueError: + msg = resp + raise ClientException("failed to delete nst {} - {}".format(name, msg)) + + def create(self, filename, overwrite=None, update_endpoint=None): + mime_type = magic.from_file(filename, mime=True) + if mime_type is None: + raise ClientException( + "failed to guess MIME type for file '{}'".format(filename)) + headers= self._client._headers + if mime_type in ['application/yaml', 'text/plain']: + headers['Content-Type'] = 'application/yaml' + elif mime_type in ['application/gzip', 'application/x-gzip']: + headers['Content-Type'] = 'application/gzip' + #headers['Content-Type'] = 'application/binary' + # Next three lines are to be removed in next version + #headers['Content-Filename'] = basename(filename) + #file_size = stat(filename).st_size + #headers['Content-Range'] = 'bytes 0-{}/{}'.format(file_size - 1, file_size) + else: + raise ClientException( + "Unexpected MIME type for file {}: MIME type {}".format( + filename, mime_type) + ) + headers["Content-File-MD5"] = utils.md5(filename) + http_header = ['{}: {}'.format(key,val) + for (key,val) in list(headers.items())] + self._http.set_http_header(http_header) + if update_endpoint: + http_code, resp = self._http.put_cmd(endpoint=update_endpoint, filename=filename) + else: + ow_string = '' + if overwrite: + ow_string = '?{}'.format(overwrite) + self._apiResource = '/netslice_templates_content' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + endpoint = '{}{}'.format(self._apiBase,ow_string) + http_code, resp = self._http.post_cmd(endpoint=endpoint, filename=filename) + #print 'HTTP CODE: {}'.format(http_code) + #print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + if resp: + resp = json.loads(resp) + if not resp or 'id' not in resp: + raise ClientException('unexpected response from server - {}'.format( + resp)) + print(resp['id']) + else: + msg = "Error {}".format(http_code) + if resp: + try: + msg = "{} - {}".format(msg, json.loads(resp)) + except ValueError: + msg = "{} - {}".format(msg, resp) + raise ClientException("failed to create/update nst - {}".format(msg)) + + def update(self, name, filename): + nst = self.get(name) + endpoint = '{}/{}/netslice_templates_content'.format(self._apiBase, nst['_id']) + self.create(filename=filename, update_endpoint=endpoint) + -- 2.17.1