From: garciadeblas Date: Mon, 4 Nov 2019 23:40:11 +0000 (+0100) Subject: New commands for managing K8s cluster and repos X-Git-Tag: v7.0.0rc1~8 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2Fosmclient.git;a=commitdiff_plain;h=e79f0cafb1e4f8b065708187c6dd0f58b56a2aeb New commands for managing K8s cluster and repos New options for vnf-show to show details of a kdu in a VNF instance Use of textwrapper to wrap text of the prettytables for vnf-show, ns-show, k8scluster-show, ns-list and vim-show Updated trunc_text function to fix truncation to lenghth Change-Id: I6f61533d6eff267d7858bb367e41e28dca8447ac Signed-off-by: garciadeblas --- diff --git a/osmclient/common/wait.py b/osmclient/common/wait.py index 96cb851..e3152af 100644 --- a/osmclient/common/wait.py +++ b/osmclient/common/wait.py @@ -28,6 +28,7 @@ TIMEOUT_GENERIC_OPERATION = 600 TIMEOUT_NSI_OPERATION = TIMEOUT_GENERIC_OPERATION TIMEOUT_SDNC_OPERATION = TIMEOUT_GENERIC_OPERATION TIMEOUT_VIM_OPERATION = TIMEOUT_GENERIC_OPERATION +TIMEOUT_K8S_OPERATION = TIMEOUT_GENERIC_OPERATION TIMEOUT_WIM_OPERATION = TIMEOUT_GENERIC_OPERATION TIMEOUT_NS_OPERATION = 3600 POLLING_TIME_INTERVAL = 5 diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index b1df0a5..d66bcfe 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -27,6 +27,20 @@ import json import time import pycurl import os +import textwrap + + +def wrap_text(text, width): + wrapper = textwrap.TextWrapper(width=width) + lines = text.splitlines() + return "\n".join(map(wrapper.fill, lines)) + + +def trunc_text(text, length): + if len(text) > length: + return text[:(length - 3)] + '...' + else: + return text def check_client_version(obj, what, version='sol005'): @@ -203,8 +217,10 @@ def ns_list(ctx, filter): opstatus = nsr['operational-status'] if 'operational-status' in nsr else 'Not found' configstatus = nsr['config-status'] if 'config-status' in nsr else 'Not found' detailed_status = nsr['detailed-status'] if 'detailed-status' in nsr else 'Not found' + detailed_status = wrap_text(text=detailed_status,width=50) if configstatus == "config_not_needed": configstatus = "configured (no charms)" + table.add_row( [nsr_name, nsr_id, @@ -457,9 +473,13 @@ def ns_op_list(ctx, name): print(str(e)) exit(1) - table = PrettyTable(['id', 'operation', 'status']) + table = PrettyTable(['id', 'operation', 'action_name', 'status']) + #print(yaml.safe_dump(resp)) for op in resp: - table.add_row([op['id'], op['lcmOperationType'], + action_name = "N/A" + if op['lcmOperationType']=='action': + action_name = op['operationParams']['primitive'] + table.add_row([op['id'], op['lcmOperationType'], action_name, op['operationState']]) table.align = 'l' print(table) @@ -753,7 +773,7 @@ def ns_show(ctx, name, literal, filter): for k, v in list(ns.items()): if filter is None or filter in k: - table.add_row([k, json.dumps(v, indent=2)]) + table.add_row([k, wrap_text(text=json.dumps(v, indent=2),width=100)]) fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ if fullclassname != 'osmclient.sol005.client.Client': @@ -761,7 +781,7 @@ def ns_show(ctx, name, literal, filter): nsr_optdata = nsopdata['nsr:nsr'] for k, v in list(nsr_optdata.items()): if filter is None or filter in k: - table.add_row([k, json.dumps(v, indent=2)]) + table.add_row([k, wrap_text(json.dumps(v, indent=2),width=100)]) table.align = 'l' print(table) @@ -770,31 +790,57 @@ def ns_show(ctx, name, literal, filter): @click.argument('name') @click.option('--literal', is_flag=True, help='print literally, no pretty table') -@click.option('--filter', default=None) +@click.option('--filter', default=None, help='restricts the information to the fields in the filter') +@click.option('--kdu', default=None, help='KDU name (whose status will be shown)') @click.pass_context -def vnf_show(ctx, name, literal, filter): +def vnf_show(ctx, name, literal, filter, kdu): """shows the info of a VNF instance NAME: name or ID of the VNF instance """ + if kdu: + if literal: + raise ClientException('"--literal" option is incompatible with "--kdu" option') + if filter: + raise ClientException('"--filter" option is incompatible with "--kdu" option') + try: check_client_version(ctx.obj, ctx.command.name) resp = ctx.obj.vnf.get(name) + + if kdu: + ns_id = resp['nsr-id-ref'] + op_data={} + op_data['member_vnf_index'] = resp['member-vnf-index-ref'] + op_data['kdu_name'] = kdu + op_data['primitive'] = 'status' + op_data['primitive_params'] = {} + op_id = ctx.obj.ns.exec_op(ns_id, op_name='action', op_data=op_data, wait=False) + t = 0 + while t<30: + op_info = ctx.obj.ns.get_op(op_id) + if op_info['operationState'] == 'COMPLETED': + print(op_info['detailed-status']) + return + time.sleep(5) + t += 5 + print ("Could not determine KDU status") + + if literal: + print(yaml.safe_dump(resp)) + return + + table = PrettyTable(['field', 'value']) + + for k, v in list(resp.items()): + if filter is None or filter in k: + table.add_row([k, wrap_text(text=json.dumps(v,indent=2),width=100)]) + table.align = 'l' + print(table) except ClientException as e: print(str(e)) exit(1) - if literal: - print(yaml.safe_dump(resp)) - return - - table = PrettyTable(['field', 'value']) - for k, v in list(resp.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='vnf-monitoring-show') #@click.argument('vnf_name') @@ -845,8 +891,10 @@ def vnf_show(ctx, name, literal, filter): @cli.command(name='ns-op-show', short_help='shows the info of a NS operation') @click.argument('id') @click.option('--filter', default=None) +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') @click.pass_context -def ns_op_show(ctx, id, filter): +def ns_op_show(ctx, id, filter, literal): """shows the detailed info of a NS operation ID: operation identifier @@ -858,10 +906,14 @@ def ns_op_show(ctx, id, filter): print(str(e)) exit(1) + if literal: + print(yaml.safe_dump(op_info)) + return + 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.add_row([k, wrap_text(json.dumps(v, indent=2), 100)]) table.align = 'l' print(table) @@ -881,7 +933,7 @@ def nst_show(ctx, name, literal): table = PrettyTable(['field', 'value']) for k, v in list(resp.items()): - table.add_row([k, json.dumps(v, indent=2)]) + table.add_row([k, wrap_text(json.dumps(v, indent=2), 100)]) table.align = 'l' print(table) @@ -1876,7 +1928,7 @@ def vim_show(ctx, name): table = PrettyTable(['key', 'attribute']) for k, v in list(resp.items()): - table.add_row([k, json.dumps(v, indent=2)]) + table.add_row([k, wrap_text(text=json.dumps(v, indent=2),width=100)]) table.align = 'l' print(table) @@ -2257,6 +2309,338 @@ def sdnc_show(ctx, name): print(table) +########################### +# K8s cluster operations +########################### + +@cli.command(name='k8scluster-add') +@click.argument('name') +@click.option('--creds', + prompt=True, + help='credentials file, i.e. a valid `.kube/config` file') +@click.option('--version', + prompt=True, + help='Kubernetes version') +@click.option('--vim', + prompt=True, + help='VIM target, the VIM where the cluster resides') +@click.option('--k8s-nets', + prompt=True, + help='list of VIM networks, in JSON inline format, where the cluster is accessible via L3 routing, e.g. "{(k8s_net1:vim_network1) [,(k8s_net2:vim_network2) ...]}"') +@click.option('--description', + default='', + help='human readable description') +@click.option('--namespace', + default='kube-system', + help='namespace to be used for its operation, defaults to `kube-system`') +@click.option('--cni', + default=None, + help='list of CNI plugins, in JSON inline format, used in the cluster') +#@click.option('--skip-init', +# is_flag=True, +# help='If set, K8s cluster is assumed to be ready for its use with OSM') +#@click.option('--wait', +# is_flag=True, +# help='do not return the control immediately, but keep it \ +# until the operation is completed, or timeout') +@click.pass_context +def k8scluster_add(ctx, + name, + creds, + version, + vim, + k8s_nets, + description, + namespace, + cni): + """adds a K8s cluster to OSM + + NAME: name of the K8s cluster + """ + try: + check_client_version(ctx.obj, ctx.command.name) + cluster = {} + cluster['name'] = name + with open(creds, 'r') as cf: + cluster['credentials'] = yaml.safe_load(cf.read()) + cluster['k8s_version'] = version + cluster['vim_account'] = vim + cluster['nets'] = yaml.safe_load(k8s_nets) + cluster['description'] = description + if namespace: cluster['namespace'] = namespace + if cni: cluster['cni'] = yaml.safe_load(cni) + ctx.obj.k8scluster.create(name, cluster) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='k8scluster-update', short_help='updates a K8s cluster') +@click.argument('name') +@click.option('--newname', help='New name for the K8s cluster') +@click.option('--creds', help='credentials file, i.e. a valid `.kube/config` file') +@click.option('--version', help='Kubernetes version') +@click.option('--vim', help='VIM target, the VIM where the cluster resides') +@click.option('--k8s-nets', help='list of VIM networks, in JSON inline format, where the cluster is accessible via L3 routing, e.g. "{(k8s_net1:vim_network1) [,(k8s_net2:vim_network2) ...]}"') +@click.option('--description', help='human readable description') +@click.option('--namespace', help='namespace to be used for its operation, defaults to `kube-system`') +@click.option('--cni', help='list of CNI plugins, in JSON inline format, used in the cluster') +@click.pass_context +def k8scluster_update(ctx, + name, + newname, + creds, + version, + vim, + k8s_nets, + description, + namespace, + cni): + """updates a K8s cluster + + NAME: name or ID of the K8s cluster + """ + try: + check_client_version(ctx.obj, ctx.command.name) + cluster = {} + if newname: cluster['name'] = newname + if creds: + with open(creds, 'r') as cf: + cluster['credentials'] = yaml.safe_load(cf.read()) + if version: cluster['k8s_version'] = version + if vim: cluster['vim_account'] = vim + if k8s_nets: cluster['nets'] = yaml.safe_load(k8s_nets) + if description: cluster['description'] = description + if namespace: cluster['namespace'] = namespace + if cni: cluster['cni'] = yaml.safe_load(cni) + ctx.obj.k8scluster.update(name, cluster) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='k8scluster-delete') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion from the DB (not recommended)') +#@click.option('--wait', +# is_flag=True, +# help='do not return the control immediately, but keep it \ +# until the operation is completed, or timeout') +@click.pass_context +def k8scluster_delete(ctx, name, force): + """deletes a K8s cluster + + NAME: name or ID of the K8s cluster to be deleted + """ + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.k8scluster.delete(name, force=force) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='k8scluster-list') +@click.option('--filter', default=None, + help='restricts the list to the K8s clusters matching the filter') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.pass_context +def k8scluster_list(ctx, filter, literal): + """list all K8s clusters""" + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.k8scluster.list(filter) + if literal: + print(yaml.safe_dump(resp)) + return + table = PrettyTable(['Name', 'Id', 'Version', 'VIM', 'K8s-nets', 'Description']) + for cluster in resp: + table.add_row([cluster['name'], cluster['_id'], cluster['k8s_version'], cluster['vim_account'], + json.dumps(cluster['nets']), trunc_text(cluster.get('description',''),40) + ]) + table.align = 'l' + print(table) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='k8scluster-show') +@click.argument('name') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.pass_context +def k8scluster_show(ctx, name, literal): + """shows the details of a K8s cluster + + NAME: name or ID of the K8s cluster + """ + try: + resp = ctx.obj.k8scluster.get(name) + if literal: + print(yaml.safe_dump(resp)) + return + table = PrettyTable(['key', 'attribute']) + for k, v in list(resp.items()): + table.add_row([k, wrap_text(text=json.dumps(v, indent=2),width=100)]) + table.align = 'l' + print(table) + except ClientException as e: + print(str(e)) + exit(1) + + + +########################### +# Repo operations +########################### + +@cli.command(name='repo-add') +@click.argument('name') +@click.argument('uri') +@click.option('--type', + type=click.Choice(['chart', 'bundle']), + prompt=True, + help='type of repo (chart for helm-charts, bundle for juju-bundles)') +@click.option('--description', + default='', + help='human readable description') +#@click.option('--wait', +# is_flag=True, +# help='do not return the control immediately, but keep it \ +# until the operation is completed, or timeout') +@click.pass_context +def repo_add(ctx, + name, + uri, + type, + description): + """adds a repo to OSM + + NAME: name of the repo + URI: URI of the repo + """ + try: + check_client_version(ctx.obj, ctx.command.name) + repo = {} + repo['name'] = name + repo['url'] = uri + repo['type'] = type + repo['description'] = description + ctx.obj.repo.create(name, repo) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='repo-update') +@click.argument('name') +@click.option('--newname', help='New name for the repo') +@click.option('--uri', help='URI of the repo') +@click.option('--type', type=click.Choice(['chart', 'bundle']), + help='type of repo (chart for helm-charts, bundle for juju-bundles)') +@click.option('--description', help='human readable description') +#@click.option('--wait', +# is_flag=True, +# help='do not return the control immediately, but keep it \ +# until the operation is completed, or timeout') +@click.pass_context +def repo_update(ctx, + name, + newname, + uri, + type, + description): + """updates a repo in OSM + + NAME: name of the repo + """ + try: + check_client_version(ctx.obj, ctx.command.name) + repo = {} + if newname: repo['name'] = newname + if uri: repo['uri'] = uri + if type: repo['type'] = type + if description: repo['description'] = description + ctx.obj.repo.update(name, repo) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='repo-delete') +@click.argument('name') +@click.option('--force', is_flag=True, help='forces the deletion from the DB (not recommended)') +#@click.option('--wait', +# is_flag=True, +# help='do not return the control immediately, but keep it \ +# until the operation is completed, or timeout') +@click.pass_context +def repo_delete(ctx, name, force): + """deletes a repo + + NAME: name or ID of the repo to be deleted + """ + try: + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.repo.delete(name, force=force) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='repo-list') +@click.option('--filter', default=None, + help='restricts the list to the repos matching the filter') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.pass_context +def repo_list(ctx, filter, literal): + """list all repos""" + try: + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.repo.list(filter) + if literal: + print(yaml.safe_dump(resp)) + return + table = PrettyTable(['Name', 'Id', 'Type', 'URI', 'Description']) + for repo in resp: + #cluster['k8s-nets'] = json.dumps(yaml.safe_load(cluster['k8s-nets'])) + table.add_row([repo['name'], repo['_id'], repo['type'], repo['url'], trunc_text(repo.get('description',''),40)]) + table.align = 'l' + print(table) + except ClientException as e: + print(str(e)) + exit(1) + + +@cli.command(name='repo-show') +@click.argument('name') +@click.option('--literal', is_flag=True, + help='print literally, no pretty table') +@click.pass_context +def repo_show(ctx, name, literal): + """shows the details of a repo + + NAME: name or ID of the repo + """ + try: + resp = ctx.obj.repo.get(name) + if literal: + print(yaml.safe_dump(resp)) + return + table = PrettyTable(['key', 'attribute']) + for k, v in list(resp.items()): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = 'l' + print(table) + except ClientException as e: + print(str(e)) + exit(1) + + + #################### # Project mgmt operations #################### @@ -2805,10 +3189,12 @@ def upload_package(ctx, filename): @cli.command(name='ns-action', short_help='executes an action/primitive over a NS instance') @click.argument('ns_name') @click.option('--vnf_name', default=None, help='member-vnf-index if the target is a vnf instead of a ns)') -@click.option('--vdu_id', default=None, help='vdu-id if the target is a vdu o a vnf') +@click.option('--kdu_name', default=None, help='kdu-name if the target is a kdu)') +@click.option('--vdu_id', default=None, help='vdu-id if the target is a vdu') @click.option('--vdu_count', default=None, help='number of vdu instance of this vdu_id') -@click.option('--action_name', prompt=True) -@click.option('--params', default=None) +@click.option('--action_name', prompt=True, help='action name') +@click.option('--params', default=None, help='action params in YAML/JSON inline string') +@click.option('--params_file', default=None, help='YAML/JSON file with action params') @click.option('--wait', required=False, default=False, @@ -2819,10 +3205,12 @@ def upload_package(ctx, filename): def ns_action(ctx, ns_name, vnf_name, + kdu_name, vdu_id, vdu_count, action_name, params, + params_file, wait): """executes an action/primitive over a NS instance @@ -2833,16 +3221,21 @@ def ns_action(ctx, op_data = {} if vnf_name: op_data['member_vnf_index'] = vnf_name + if kdu_name: + op_data['kdu_name'] = kdu_name if vdu_id: op_data['vdu_id'] = vdu_id if vdu_count: op_data['vdu_count_index'] = vdu_count op_data['primitive'] = action_name + if params_file: + with open(params_file, 'r') as pf: + params = pf.read() if params: op_data['primitive_params'] = yaml.safe_load(params) else: op_data['primitive_params'] = {} - ctx.obj.ns.exec_op(ns_name, op_name='action', op_data=op_data, wait=wait) + print(ctx.obj.ns.exec_op(ns_name, op_name='action', op_data=op_data, wait=wait)) except ClientException as e: print(str(e)) diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py index 6281ffe..482cd73 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -34,6 +34,8 @@ from osmclient.sol005 import project as projectmodule from osmclient.sol005 import user as usermodule from osmclient.sol005 import role from osmclient.sol005 import pdud +from osmclient.sol005 import k8scluster +from osmclient.sol005 import repo from osmclient.common.exceptions import ClientException from osmclient.common import package_tool import json @@ -87,12 +89,13 @@ 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.k8scluster = k8scluster.K8scluster(self._http_client, client=self) + self.repo = repo.Repo(self._http_client, client=self) + self.package_tool = package_tool.PackageTool(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/sol005/k8scluster.py b/osmclient/sol005/k8scluster.py new file mode 100644 index 0000000..5520787 --- /dev/null +++ b/osmclient/sol005/k8scluster.py @@ -0,0 +1,140 @@ +# +# 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 K8s cluster API handling +""" + +from osmclient.common import utils +from osmclient.common.exceptions import ClientException +from osmclient.common.exceptions import NotFound +import json + +class K8scluster(object): + def __init__(self, http=None, client=None): + self._http = http + self._client = client + self._apiName = '/admin' + self._apiVersion = '/v1' + self._apiResource = '/k8sclusters' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + + def create(self, name, k8s_cluster): + + def get_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)) + return vim['_id'] + + self._client.get_token() + k8s_cluster['vim_account'] = get_vim_account_id(k8s_cluster['vim_account']) + http_code, resp = self._http.post_cmd(endpoint=self._apiBase, + postfields_dict=k8s_cluster) + #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("failed to add K8s cluster {} - {}".format(name, msg)) + + def update(self, name, k8s_cluster): + self._client.get_token() + cluster = self.get(name) + http_code, resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,cluster['_id']), + postfields_dict=k8s_cluster) + # print 'HTTP CODE: {}'.format(http_code) + # print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + pass + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException("failed to update K8s cluster {} - {}".format(name, msg)) + + def get_id(self, name): + """Returns a K8s cluster id from a K8s cluster name + """ + for cluster in self.list(): + if name == cluster['name']: + return cluster['_id'] + raise NotFound("K8s cluster {} not found".format(name)) + + def delete(self, name, force=False): + self._client.get_token() + cluster_id = name + if not utils.validate_uuid4(name): + cluster_id = self.get_id(name) + querystring = '' + if force: + querystring = '?FORCE=True' + http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase, + cluster_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 K8s cluster {} - {}".format(name, msg)) + + def list(self, filter=None): + """Returns a list of K8s clusters + """ + self._client.get_token() + 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 a K8s cluster based on name or id + """ + self._client.get_token() + cluster_id = name + if not utils.validate_uuid4(name): + cluster_id = self.get_id(name) + resp = self._http.get_cmd('{}/{}'.format(self._apiBase,cluster_id)) + if not resp or '_id' not in resp: + raise ClientException('failed to get K8s cluster info: '.format(resp)) + else: + return resp + raise NotFound("K8s cluster {} not found".format(name)) + diff --git a/osmclient/sol005/ns.py b/osmclient/sol005/ns.py index 8a9c9f0..1165b3d 100644 --- a/osmclient/sol005/ns.py +++ b/osmclient/sol005/ns.py @@ -314,11 +314,11 @@ class Ns(object): str(exc)) raise ClientException(message) - def exec_op(self, name, op_name, op_data=None, wait=False): + def exec_op(self, name, op_name, op_data=None, wait=False, ): """Executes an operation on a NS """ - ns = self.get(name) try: + ns = self.get(name) self._apiResource = '/ns_instances' self._apiBase = '{}{}{}'.format(self._apiName, self._apiVersion, self._apiResource) @@ -338,7 +338,7 @@ class Ns(object): # Wait for status for NS instance action # For the 'action' operation, 'id' is used self._wait(resp.get('id')) - print(resp['id']) + return resp['id'] else: msg = "" if resp: @@ -369,7 +369,8 @@ class Ns(object): "member-vnf-index": vnf_name, "scaling-group-descriptor": scaling_group, } - self.exec_op(ns_name, op_name='scale', op_data=op_data, wait=wait) + op_id = self.exec_op(ns_name, op_name='scale', op_data=op_data, wait=wait) + print(str(op_id)) except ClientException as exc: message="failed to scale vnf {} of ns {}:\nerror:\n{}".format( vnf_name, ns_name, str(exc)) diff --git a/osmclient/sol005/repo.py b/osmclient/sol005/repo.py new file mode 100644 index 0000000..a8d8f17 --- /dev/null +++ b/osmclient/sol005/repo.py @@ -0,0 +1,133 @@ +# +# 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 Repo API handling +""" + +from osmclient.common import utils +from osmclient.common.exceptions import ClientException +from osmclient.common.exceptions import NotFound +import json + +class Repo(object): + def __init__(self, http=None, client=None): + self._http = http + self._client = client + self._apiName = '/admin' + self._apiVersion = '/v1' + self._apiResource = '/k8srepos' + self._apiBase = '{}{}{}'.format(self._apiName, + self._apiVersion, self._apiResource) + + def create(self, name, repo): + self._client.get_token() + http_code, resp = self._http.post_cmd(endpoint=self._apiBase, + postfields_dict=repo) + #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("failed to add repo {} - {}".format(name, msg)) + + def update(self, name, repo): + self._client.get_token() + repo_dict = self.get(name) + http_code, resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,repo_dict['_id']), + postfields_dict=repo) + # print 'HTTP CODE: {}'.format(http_code) + # print 'RESP: {}'.format(resp) + if http_code in (200, 201, 202, 204): + pass + else: + msg = "" + if resp: + try: + msg = json.loads(resp) + except ValueError: + msg = resp + raise ClientException("failed to update repo {} - {}".format(name, msg)) + + def get_id(self, name): + """Returns a repo id from a repo name + """ + self._client.get_token() + for repo in self.list(): + if name == repo['name']: + return repo['_id'] + raise NotFound("Repo {} not found".format(name)) + + def delete(self, name, force=False): + self._client.get_token() + repo_id = name + if not utils.validate_uuid4(name): + repo_id = self.get_id(name) + querystring = '' + if force: + querystring = '?FORCE=True' + http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase, + repo_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 repo {} - {}".format(name, msg)) + + def list(self, filter=None): + """Returns a list of repos + """ + self._client.get_token() + 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 a repo based on name or id + """ + self._client.get_token() + repo_id = name + if not utils.validate_uuid4(name): + repo_id = self.get_id(name) + resp = self._http.get_cmd('{}/{}'.format(self._apiBase,repo_id)) + if not resp or '_id' not in resp: + raise ClientException('failed to get repo info: '.format(resp)) + else: + return resp + raise NotFound("Repo {} not found".format(name)) +