New commands for managing K8s cluster and repos 13/8213/3
authorgarciadeblas <gerardo.garciadeblas@telefonica.com>
Mon, 4 Nov 2019 23:40:11 +0000 (00:40 +0100)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Mon, 25 Nov 2019 22:13:53 +0000 (22:13 +0000)
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 <gerardo.garciadeblas@telefonica.com>
osmclient/common/wait.py
osmclient/scripts/osm.py
osmclient/sol005/client.py
osmclient/sol005/k8scluster.py [new file with mode: 0644]
osmclient/sol005/ns.py
osmclient/sol005/repo.py [new file with mode: 0644]

index 96cb851..e3152af 100644 (file)
@@ -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_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
 TIMEOUT_WIM_OPERATION = TIMEOUT_GENERIC_OPERATION
 TIMEOUT_NS_OPERATION = 3600
 POLLING_TIME_INTERVAL = 5
index b1df0a5..d66bcfe 100755 (executable)
@@ -27,6 +27,20 @@ import json
 import time
 import pycurl
 import os
 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'):
 
 
 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'
         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)"
         if configstatus == "config_not_needed":
             configstatus = "configured (no charms)"
+
         table.add_row(
             [nsr_name,
              nsr_id,
         table.add_row(
             [nsr_name,
              nsr_id,
@@ -457,9 +473,13 @@ def ns_op_list(ctx, name):
         print(str(e))
         exit(1)
 
         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:
     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)
                        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:
 
     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':
 
     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:
         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)
 
     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.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
 @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
     """
     """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)
     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)
 
     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')
 
 #@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)
 @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
 @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
     """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)
 
         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 = 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)
 
     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 = 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)
 
     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 = 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)
 
     table.align = 'l'
     print(table)
 
@@ -2257,6 +2309,338 @@ def sdnc_show(ctx, name):
     print(table)
 
 
     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
 ####################
 ####################
 # 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)')
 @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('--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,
 @click.option('--wait',
               required=False,
               default=False,
@@ -2819,10 +3205,12 @@ def upload_package(ctx, filename):
 def ns_action(ctx,
               ns_name,
               vnf_name,
 def ns_action(ctx,
               ns_name,
               vnf_name,
+              kdu_name,
               vdu_id,
               vdu_count,
               action_name,
               params,
               vdu_id,
               vdu_count,
               action_name,
               params,
+              params_file,
               wait):
     """executes an action/primitive over a NS instance
 
               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
         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 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'] = {}
         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))
 
     except ClientException as e:
         print(str(e))
index 6281ffe..482cd73 100644 (file)
@@ -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 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
 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.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.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:
 
     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 (file)
index 0000000..5520787
--- /dev/null
@@ -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))
+
index 8a9c9f0..1165b3d 100644 (file)
@@ -314,11 +314,11 @@ class Ns(object):
                     str(exc))
             raise ClientException(message)
 
                     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
         """
         """Executes an operation on a NS
         """
-        ns = self.get(name)
         try:
         try:
+            ns = self.get(name)
             self._apiResource = '/ns_instances'
             self._apiBase = '{}{}{}'.format(self._apiName,
                                             self._apiVersion, self._apiResource)
             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'))
                     # 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:
             else:
                 msg = ""
                 if resp:
@@ -369,7 +369,8 @@ class Ns(object):
                 "member-vnf-index": vnf_name,
                 "scaling-group-descriptor": scaling_group,
             }
                 "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))
         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 (file)
index 0000000..a8d8f17
--- /dev/null
@@ -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))
+