-# Copyright 2017 Sandvine
+# Copyright 2017-2018 Sandvine
+# Copyright 2018 Telefonica
#
# All Rights Reserved.
#
OSM client entry point
"""
-from osmclient.v1 import client
+from osmclient.v1 import client as client
+from osmclient.sol005 import client as sol005client
-
-def Client(version=1, host=None, *args, **kwargs):
- if version == 1:
- return client.Client(host, *args, **kwargs)
+def Client(version=1, host=None, sol005=False, *args, **kwargs):
+ if not sol005:
+ if version == 1:
+ return client.Client(host, *args, **kwargs)
+ else:
+ raise Exception("Unsupported client version")
else:
- raise Exception("Unsupported client version")
+ if version == 1:
+ return sol005client.Client(host, *args, **kwargs)
+ else:
+ raise Exception("Unsupported client version")
+
# under the License.
import time
-
+from uuid import UUID
+import hashlib
+import tarfile
+import re
+import yaml
def wait_for_value(func, result=True, wait_time=10, catch_exception=None):
maxtime = time.time() + wait_time
return func() == result
except catch_exception:
return False
+
+
+def validate_uuid4(uuid_text):
+ try:
+ UUID(uuid_text)
+ return True
+ except (ValueError, TypeError):
+ return False
+
+
+def md5(fname):
+ hash_md5 = hashlib.md5()
+ with open(fname, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash_md5.update(chunk)
+ return hash_md5.hexdigest()
+
+
+def get_key_val_from_pkg(descriptor_file):
+# method opens up a package and finds the name of the resulting
+# descriptor (vnfd or nsd name)
+ tar = tarfile.open(descriptor_file)
+ yamlfile = None
+ for member in tar.getmembers():
+ if (re.match('.*.yaml', member.name) and
+ len(member.name.split('/')) == 2):
+ yamlfile = member.name
+ break
+ if yamlfile is None:
+ return None
+
+ dict = yaml.load(tar.extractfile(yamlfile))
+ result = {}
+ for k1, v1 in dict.items():
+ if not k1.endswith('-catalog'):
+ continue
+ for k2, v2 in v1.items():
+ if not k2.endswith('nsd') and not k2.endswith('vnfd'):
+ continue
+
+ if 'nsd' in k2:
+ result['type'] = 'nsd'
+ else:
+ result['type'] = 'vnfd'
+
+ for entry in v2:
+ for k3, v3 in entry.items():
+ # strip off preceeding chars before :
+ key_name = k3.split(':').pop()
+
+ result[key_name] = v3
+ tar.close()
+ return result
+
-# Copyright 2017 Sandvine
+# Copyright 2017-2018 Sandvine
+# Copyright 2018 Telefonica
#
# All Rights Reserved.
#
"""
import click
-from osmclient.client import client
+from osmclient import client
from osmclient.common.exceptions import ClientException
from prettytable import PrettyTable
+import yaml
import json
import time
+def check_client_version(obj, what, version='sol005'):
+ '''
+ Checks the version of the client object and raises error if it not the expected.
+
+ :param obj: the client object
+ :what: the function or command under evaluation (used when an error is raised)
+ :return: -
+ :raises ClientError: if the specified version does not match the client version
+ '''
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ message = 'the following commands or options are only supported with the option "--sol005": {}'.format(what)
+ if version == 'v1':
+ message = 'the following commands or options are not supported when using option "--sol005": {}'.format(what)
+ if fullclassname != 'osmclient.{}.client.Client'.format(version):
+ raise ClientException(message)
+ return
@click.group()
@click.option('--hostname',
help='hostname of server. ' +
'Also can set OSM_HOSTNAME in environment')
@click.option('--so-port',
- default=8008,
+ default=None,
envvar='OSM_SO_PORT',
help='hostname of server. ' +
'Also can set OSM_SO_PORT in environment')
@click.option('--so-project',
- default='default',
+ default=None,
envvar='OSM_SO_PROJECT',
help='Project Name in SO. ' +
'Also can set OSM_SO_PROJECT in environment')
envvar='OSM_RO_PORT',
help='hostname of RO server. ' +
'Also can set OSM_RO_PORT in environment')
+@click.option('--sol005',
+ is_flag=True,
+ envvar='OSM_SOL005',
+ help='Use ETSI NFV SOL005 API')
@click.pass_context
-def cli(ctx, hostname, so_port, so_project, ro_hostname, ro_port):
+def cli(ctx, hostname, so_port, so_project, ro_hostname, ro_port, sol005):
if hostname is None:
print(
"either hostname option or OSM_HOSTNAME " +
"environment variable needs to be specified")
exit(1)
- ctx.obj = client.Client(
- host=hostname,
- so_port=so_port,
- so_project=so_project,
- ro_host=ro_hostname,
- ro_port=ro_port)
-
+ kwargs={}
+ if so_port is not None:
+ kwargs['so_port']=so_port
+ if so_project is not None:
+ kwargs['so_project']=so_project
+ if ro_hostname is not None:
+ kwargs['ro_host']=ro_hostname
+ if ro_port is not None:
+ kwargs['ro_port']=ro_port
+
+ ctx.obj = client.Client(host=hostname, sol005=sol005, **kwargs)
+
+
+####################
+# LIST operations
+####################
@cli.command(name='ns-list')
+@click.option('--filter', default=None,
+ help='restricts the list to the NS instances matching the filter')
@click.pass_context
-def ns_list(ctx):
- resp = ctx.obj.ns.list()
+def ns_list(ctx, filter):
+ '''list all NS instances'''
+ if filter:
+ check_client_version(ctx.obj, '--filter option')
+ resp = ctx.obj.ns.list(filter)
+ else:
+ resp = ctx.obj.ns.list()
table = PrettyTable(
['ns instance name',
'id',
'operational status',
'config status'])
for ns in resp:
- nsopdata = ctx.obj.ns.get_opdata(ns['id'])
- nsr = nsopdata['nsr:nsr']
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname == 'osmclient.sol005.client.Client':
+ nsr = ns
+ else:
+ nsopdata = ctx.obj.ns.get_opdata(ns['id'])
+ nsr = nsopdata['nsr:nsr']
+ opstatus = nsr['operational-status'] if 'operational-status' in nsr else 'Not found'
+ configstatus = nsr['config-status'] if 'config-status' in nsr else 'Not found'
+ if configstatus == "config_not_needed":
+ configstatus = "configured (no charms)"
table.add_row(
- [nsr['name-ref'],
- nsr['ns-instance-config-ref'],
- nsr['operational-status'],
- nsr['config-status']])
+ [nsr['name'],
+ nsr['_id'],
+ opstatus,
+ configstatus])
table.align = 'l'
print(table)
-@cli.command(name='nsd-list')
-@click.pass_context
-def nsd_list(ctx):
- resp = ctx.obj.nsd.list()
+def nsd_list(ctx, filter):
+ if filter:
+ check_client_version(ctx.obj, '--filter')
+ resp = ctx.obj.nsd.list(filter)
+ else:
+ resp = ctx.obj.nsd.list()
+ #print yaml.safe_dump(resp)
table = PrettyTable(['nsd name', 'id'])
- for ns in resp:
- table.add_row([ns['name'], ns['id']])
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname == 'osmclient.sol005.client.Client':
+ for ns in resp:
+ name = ns['name'] if 'name' in ns else '-'
+ table.add_row([name, ns['_id']])
+ else:
+ for ns in resp:
+ table.add_row([ns['name'], ns['id']])
table.align = 'l'
print(table)
-@cli.command(name='vnfd-list')
+@cli.command(name='nsd-list')
+@click.option('--filter', default=None,
+ help='restricts the list to the NSD/NSpkg matching the filter')
@click.pass_context
-def vnfd_list(ctx):
- resp = ctx.obj.vnfd.list()
+def nsd_list1(ctx, filter):
+ '''list all NSD/NSpkg in the system'''
+ nsd_list(ctx,filter)
+
+
+@cli.command(name='nspkg-list')
+@click.option('--filter', default=None,
+ help='restricts the list to the NSD/NSpkg matching the filter')
+@click.pass_context
+def nsd_list2(ctx, filter):
+ '''list all NSD/NSpkg in the system'''
+ nsd_list(ctx,filter)
+
+
+def vnfd_list(ctx, filter):
+ if filter:
+ check_client_version(ctx.obj, '--filter')
+ resp = ctx.obj.vnfd.list(filter)
+ else:
+ resp = ctx.obj.vnfd.list()
+ #print yaml.safe_dump(resp)
table = PrettyTable(['vnfd name', 'id'])
- for vnfd in resp:
- table.add_row([vnfd['name'], vnfd['id']])
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname == 'osmclient.sol005.client.Client':
+ for vnfd in resp:
+ name = vnfd['name'] if 'name' in vnfd else '-'
+ table.add_row([name, vnfd['_id']])
+ else:
+ for vnfd in resp:
+ table.add_row([vnfd['name'], vnfd['id']])
table.align = 'l'
print(table)
+@cli.command(name='vnfd-list')
+@click.option('--filter', default=None,
+ help='restricts the list to the VNFD/VNFpkg matching the filter')
+@click.pass_context
+def vnfd_list1(ctx, filter):
+ '''list all VNFD/VNFpkg in the system'''
+ vnfd_list(ctx,filter)
+
+
+@cli.command(name='vnfpkg-list')
+@click.option('--filter', default=None,
+ help='restricts the list to the VNFD/VNFpkg matching the filter')
+@click.pass_context
+def vnfd_list2(ctx, filter):
+ '''list all VNFD/VNFpkg in the system'''
+ vnfd_list(ctx,filter)
+
+
@cli.command(name='vnf-list')
@click.pass_context
def vnf_list(ctx):
+ ''' list all VNF instances'''
resp = ctx.obj.vnf.list()
table = PrettyTable(
['vnf name',
print(table)
-@cli.command(name='vnf-show')
-@click.argument('vnf_name')
+####################
+# SHOW operations
+####################
+
+def nsd_show(ctx, name, literal):
+ try:
+ resp = ctx.obj.nsd.get(name)
+ #resp = ctx.obj.nsd.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 resp.items():
+ table.add_row([k, json.dumps(v, indent=2)])
+ table.align = 'l'
+ print(table)
+
+
+@cli.command(name='nsd-show', short_help='shows the content of a NSD')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
+@click.argument('name')
+@click.pass_context
+def nsd_show1(ctx, name, literal):
+ '''shows the content of a NSD
+
+ NAME: name or ID of the NSD/NSpkg
+ '''
+ nsd_show(ctx, name, literal)
+
+
+@cli.command(name='nspkg-show', short_help='shows the content of a NSD')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
+@click.argument('name')
+@click.pass_context
+def nsd_show2(ctx, name, literal):
+ '''shows the content of a NSD
+
+ NAME: name or ID of the NSD/NSpkg
+ '''
+ nsd_show(ctx, name, literal)
+
+
+def vnfd_show(ctx, name, literal):
+ try:
+ resp = ctx.obj.vnfd.get(name)
+ #resp = ctx.obj.vnfd.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 resp.items():
+ table.add_row([k, json.dumps(v, indent=2)])
+ table.align = 'l'
+ print(table)
+
+
+@cli.command(name='vnfd-show', short_help='shows the content of a VNFD')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
+@click.argument('name')
+@click.pass_context
+def vnfd_show1(ctx, name, literal):
+ '''shows the content of a VNFD
+
+ NAME: name or ID of the VNFD/VNFpkg
+ '''
+ vnfd_show(ctx, name, literal)
+
+
+@cli.command(name='vnfpkg-show', short_help='shows the content of a VNFD')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
+@click.argument('name')
+@click.pass_context
+def vnfd_show2(ctx, name, literal):
+ '''shows the content of a VNFD
+
+ NAME: name or ID of the VNFD/VNFpkg
+ '''
+ vnfd_show(ctx, name, literal)
+
+
+@cli.command(name='ns-show', short_help='shows the info of a NS instance')
+@click.argument('name')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
+@click.option('--filter', default=None)
+@click.pass_context
+def ns_show(ctx, name, literal, filter):
+ '''shows the info of a NS instance
+
+ NAME: name or ID of the NS instance
+ '''
+ try:
+ ns = ctx.obj.ns.get(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 ns.items():
+ if filter is None or filter in k:
+ table.add_row([k, json.dumps(v, indent=2)])
+
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname != 'osmclient.sol005.client.Client':
+ nsopdata = ctx.obj.ns.get_opdata(ns['id'])
+ nsr_optdata = nsopdata['nsr:nsr']
+ for k, v in nsr_optdata.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-show', short_help='shows the info of a VNF instance')
+@click.argument('name')
+@click.option('--literal', is_flag=True,
+ help='print literally, no pretty table')
@click.option('--filter', default=None)
@click.pass_context
-def vnf_show(ctx, vnf_name, filter):
+def vnf_show(ctx, name, literal, filter):
+ '''shows the info of a VNF instance
+
+ NAME: name or ID of the VNF instance
+ '''
try:
- resp = ctx.obj.vnf.get(vnf_name)
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ resp = ctx.obj.vnf.get(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 resp.items():
if filter is None or filter in k:
@click.pass_context
def vnf_monitoring_show(ctx, vnf_name):
try:
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
resp = ctx.obj.vnf.get_monitoring(vnf_name)
except ClientException as inst:
print(inst.message)
@click.pass_context
def ns_monitoring_show(ctx, ns_name):
try:
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
resp = ctx.obj.ns.get_monitoring(ns_name)
except ClientException as inst:
print(inst.message)
print(table)
+####################
+# CREATE operations
+####################
+
+def nsd_create(ctx, filename, overwrite):
+ try:
+ check_client_version(ctx.obj, ctx.command.name)
+ ctx.obj.nsd.create(filename, overwrite)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
+
+@cli.command(name='nsd-create', short_help='creates a new NSD/NSpkg')
+@click.argument('filename')
+@click.option('--overwrite', default=None,
+ help='overwrites some fields in NSD')
+@click.pass_context
+def nsd_create1(ctx, filename, overwrite):
+ '''creates a new NSD/NSpkg
+
+ FILENAME: NSD yaml file or NSpkg tar.gz file
+ '''
+ nsd_create(ctx, filename, overwrite)
+
+
+@cli.command(name='nspkg-create', short_help='creates a new NSD/NSpkg')
+@click.argument('filename')
+@click.option('--overwrite', default=None,
+ help='overwrites some fields in NSD')
+@click.pass_context
+def nsd_create2(ctx, filename, overwrite):
+ '''creates a new NSD/NSpkg
+
+ FILENAME: NSD yaml file or NSpkg tar.gz file
+ '''
+ nsd_create(ctx, filename, overwrite)
+
+
+def vnfd_create(ctx, filename, overwrite):
+ try:
+ check_client_version(ctx.obj, ctx.command.name)
+ ctx.obj.vnfd.create(filename, overwrite)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
+
+@cli.command(name='vnfd-create', short_help='creates a new VNFD/VNFpkg')
+@click.argument('filename')
+@click.option('--overwrite', default=None,
+ help='overwrites some fields in VNFD')
+@click.pass_context
+def vnfd_create1(ctx, filename, overwrite):
+ '''creates a new VNFD/VNFpkg
+
+ FILENAME: VNFD yaml file or VNFpkg tar.gz file
+ '''
+ vnfd_create(ctx, filename, overwrite)
+
+
+@cli.command(name='vnfpkg-create', short_help='creates a new VNFD/VNFpkg')
+@click.argument('filename')
+@click.option('--overwrite', default=None,
+ help='overwrites some fields in VNFD')
+@click.pass_context
+def vnfd_create2(ctx, filename, overwrite):
+ '''creates a new VNFD/VNFpkg
+
+ FILENAME: VNFD yaml file or VNFpkg tar.gz file
+ '''
+ vnfd_create(ctx, filename, overwrite)
+
+
@cli.command(name='ns-create')
@click.option('--ns_name',
prompt=True)
admin_status,
ssh_keys,
config):
+ '''creates a new NS instance'''
try:
+ if config:
+ check_client_version(ctx.obj, '--config', 'v1')
ctx.obj.ns.create(
nsd_name,
ns_name,
exit(1)
-@cli.command(name='ns-delete')
-@click.argument('ns_name')
-@click.pass_context
-def ns_delete(ctx, ns_name):
+####################
+# UPDATE operations
+####################
+
+def nsd_update(ctx, name, content):
try:
- ctx.obj.ns.delete(ns_name)
+ check_client_version(ctx.obj, ctx.command.name)
+ ctx.obj.nsd.update(name, content)
except ClientException as inst:
print(inst.message)
exit(1)
-
-@cli.command(name='upload-package')
-@click.argument('filename')
+@cli.command(name='nsd-update', short_help='updates a NSD/NSpkg')
+@click.argument('name')
+@click.option('--content', default=None,
+ help='filename with the NSD/NSpkg replacing the current one')
@click.pass_context
-def upload_package(ctx, filename):
- try:
- ctx.obj.package.upload(filename)
- ctx.obj.package.wait_for_upload(filename)
- except ClientException as inst:
- print(inst.message)
- exit(1)
+def nsd_update1(ctx, name, content):
+ '''updates a NSD/NSpkg
+ NAME: name or ID of the NSD/NSpkg
+ '''
+ nsd_update(ctx, name, content)
-@cli.command(name='ns-show')
-@click.argument('ns_name')
-@click.option('--filter', default=None)
+
+@cli.command(name='nspkg-update', short_help='updates a NSD/NSpkg')
+@click.argument('name')
+@click.option('--content', default=None,
+ help='filename with the NSD/NSpkg replacing the current one')
@click.pass_context
-def ns_show(ctx, ns_name, filter):
+def nsd_update2(ctx, name, content):
+ '''updates a NSD/NSpkg
+
+ NAME: name or ID of the NSD/NSpkg
+ '''
+ nsd_update(ctx, name, content)
+
+
+def vnfd_update(ctx, name, content):
try:
- ns = ctx.obj.ns.get(ns_name)
+ check_client_version(ctx.obj, ctx.command.name)
+ ctx.obj.vnfd.update(name, content)
except ClientException as inst:
print(inst.message)
exit(1)
- table = PrettyTable(['field', 'value'])
- for k, v in ns.items():
- if filter is None or filter in k:
- table.add_row([k, json.dumps(v, indent=2)])
+@cli.command(name='vnfd-update', short_help='updates a new VNFD/VNFpkg')
+@click.argument('name')
+@click.option('--content', default=None,
+ help='filename with the VNFD/VNFpkg replacing the current one')
+@click.pass_context
+def vnfd_update1(ctx, name, content):
+ '''updates a VNFD/VNFpkg
- nsopdata = ctx.obj.ns.get_opdata(ns['id'])
- nsr_optdata = nsopdata['nsr:nsr']
- for k, v in nsr_optdata.items():
- if filter is None or filter in k:
- table.add_row([k, json.dumps(v, indent=2)])
- table.align = 'l'
- print(table)
+ NAME: name or ID of the VNFD/VNFpkg
+ '''
+ vnfd_update(ctx, name, content)
-@cli.command(name='ns-scaling-show')
-@click.argument('ns_name')
+@cli.command(name='vnfpkg-update', short_help='updates a VNFD/VNFpkg')
+@click.argument('name')
+@click.option('--content', default=None,
+ help='filename with the VNFD/VNFpkg replacing the current one')
@click.pass_context
-def show_ns_scaling(ctx, ns_name):
- resp = ctx.obj.ns.list()
-
- table = PrettyTable(
- ['group-name',
- 'instance-id',
- 'operational status',
- 'create-time',
- 'vnfr ids'])
-
- for ns in resp:
- if ns_name == ns['name']:
- nsopdata = ctx.obj.ns.get_opdata(ns['id'])
- scaling_records = nsopdata['nsr:nsr']['scaling-group-record']
- for record in scaling_records:
- if 'instance' in record:
- instances = record['instance']
- for inst in instances:
- table.add_row(
- [record['scaling-group-name-ref'],
- inst['instance-id'],
- inst['op-status'],
- time.strftime('%Y-%m-%d %H:%M:%S',
- time.localtime(
- inst['create-time'])),
- inst['vnfrs']])
- table.align = 'l'
- print(table)
+def vnfd_update2(ctx, name, content):
+ '''updates a VNFD/VNFpkg
+ NAME: VNFD yaml file or VNFpkg tar.gz file
+ '''
+ vnfd_update(ctx, name, content)
-@cli.command(name='ns-scale')
-@click.argument('ns_name')
-@click.option('--ns_scale_group', prompt=True)
-@click.option('--index', prompt=True)
-@click.pass_context
-def ns_scale(ctx, ns_name, ns_scale_group, index):
- ctx.obj.ns.scale(ns_name, ns_scale_group, index)
+####################
+# DELETE operations
+####################
-@cli.command(name='nsd-delete')
-@click.argument('nsd_name')
-@click.pass_context
-def nsd_delete(ctx, nsd_name):
+def nsd_delete(ctx, name):
try:
- ctx.obj.nsd.delete(nsd_name)
+ ctx.obj.nsd.delete(name)
except ClientException as inst:
print(inst.message)
exit(1)
-@cli.command(name='vnfd-delete')
-@click.argument('vnfd_name')
+@cli.command(name='nsd-delete', short_help='deletes a NSD/NSpkg')
+@click.argument('name')
@click.pass_context
-def vnfd_delete(ctx, vnfd_name):
+def nsd_delete1(ctx, name):
+ '''deletes a NSD/NSpkg
+
+ NAME: name or ID of the NSD/NSpkg to be deleted
+ '''
+ nsd_delete(ctx, name)
+
+
+@cli.command(name='nspkg-delete', short_help='deletes a NSD/NSpkg')
+@click.argument('name')
+@click.pass_context
+def nsd_delete2(ctx, name):
+ '''deletes a NSD/NSpkg
+
+ NAME: name or ID of the NSD/NSpkg to be deleted
+ '''
+ nsd_delete(ctx, name)
+
+
+def vnfd_delete(ctx, name):
try:
- ctx.obj.vnfd.delete(vnfd_name)
+ ctx.obj.vnfd.delete(name)
except ClientException as inst:
print(inst.message)
exit(1)
-@cli.command(name='config-agent-list')
+@cli.command(name='vnfd-delete', short_help='deletes a VNFD/VNFpkg')
+@click.argument('name')
@click.pass_context
-def config_agent_list(ctx):
- table = PrettyTable(['name', 'account-type', 'details'])
- for account in ctx.obj.vca.list():
- table.add_row(
- [account['name'],
- account['account-type'],
- account['juju']])
- table.align = 'l'
- print(table)
+def vnfd_delete1(ctx, name):
+ '''deletes a VNFD/VNFpkg
+ NAME: name or ID of the VNFD/VNFpkg to be deleted
+ '''
+ vnfd_delete(ctx, name)
-@cli.command(name='config-agent-delete')
+
+@cli.command(name='vnfpkg-delete', short_help='deletes a VNFD/VNFpkg')
@click.argument('name')
@click.pass_context
-def config_agent_delete(ctx, name):
- try:
- ctx.obj.vca.delete(name)
- except ClientException as inst:
- print(inst.message)
- exit(1)
+def vnfd_delete2(ctx, name):
+ '''deletes a VNFD/VNFpkg
+ NAME: name or ID of the VNFD/VNFpkg to be deleted
+ '''
+ vnfd_delete(ctx, name)
-@cli.command(name='config-agent-add')
-@click.option('--name',
- prompt=True)
-@click.option('--account_type',
- prompt=True)
-@click.option('--server',
- prompt=True)
-@click.option('--user',
- prompt=True)
-@click.option('--secret',
- prompt=True,
- hide_input=True,
- confirmation_prompt=True)
+
+@cli.command(name='ns-delete', short_help='deletes a NS instance')
+@click.argument('name')
@click.pass_context
-def config_agent_add(ctx, name, account_type, server, user, secret):
+def ns_delete(ctx, name):
+ '''deletes a NS instance
+
+ NAME: name or ID of the NS instance to be deleted
+ '''
try:
- ctx.obj.vca.create(name, account_type, server, user, secret)
+ ctx.obj.ns.delete(name)
except ClientException as inst:
print(inst.message)
exit(1)
+####################
+# VIM operations
+####################
+
@cli.command(name='vim-create')
@click.option('--name',
prompt=True,
help='VIM password')
@click.option('--auth_url',
prompt=True,
- help='VIM connector url')
+ help='VIM url')
@click.option('--tenant',
prompt=True,
- help='tenant name')
+ help='VIM tenant name')
@click.option('--config',
default=None,
help='VIM specific config parameters')
default='openstack',
help='VIM type')
@click.option('--description',
- default='no description')
+ default='no description',
+ help='human readable description')
@click.pass_context
def vim_create(ctx,
name,
config,
account_type,
description):
+ '''creates a new VIM account
+ '''
vim = {}
vim['vim-username'] = user
vim['vim-password'] = password
exit(1)
+@cli.command(name='vim-update', short_help='updates a VIM account')
+@click.argument('name')
+@click.option('--newname', default=None, help='New name for the VIM account')
+@click.option('--user', default=None, help='VIM username')
+@click.option('--password', default=None, help='VIM password')
+@click.option('--auth_url', default=None, help='VIM url')
+@click.option('--tenant', default=None, help='VIM tenant name')
+@click.option('--config', default=None, help='VIM specific config parameters')
+@click.option('--account_type', default=None, help='VIM type')
+@click.option('--description', default=None, help='human readable description')
+@click.pass_context
+def vim_update(ctx,
+ name,
+ newname,
+ user,
+ password,
+ auth_url,
+ tenant,
+ config,
+ account_type,
+ description):
+ '''updates a VIM account
+
+ NAME: name or ID of the VIM account
+ '''
+ vim = {}
+ if newname:
+ vim['name'] = newname
+ vim['vim_user'] = user
+ vim['vim_password'] = password
+ vim['vim_url'] = auth_url
+ vim['vim-tenant-name'] = tenant
+ vim['config'] = config
+ vim['vim_type'] = account_type
+ vim['description'] = description
+ try:
+ check_client_version(ctx.obj, ctx.command.name)
+ ctx.obj.vim.update(name, vim)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
+
@cli.command(name='vim-delete')
@click.argument('name')
@click.pass_context
def vim_delete(ctx, name):
+ '''deletes a VIM account
+
+ NAME: name or ID of the VIM account to be deleted
+ '''
try:
ctx.obj.vim.delete(name)
except ClientException as inst:
@click.option('--ro_update/--no_ro_update',
default=False,
help='update list from RO')
+@click.option('--filter', default=None,
+ help='restricts the list to the VIM accounts matching the filter')
@click.pass_context
-def vim_list(ctx, ro_update):
- resp = ctx.obj.vim.list(ro_update)
+def vim_list(ctx, ro_update, filter):
+ '''list all VIM accounts'''
+ if filter:
+ check_client_version(ctx.obj, '--filter')
+ if ro_update:
+ check_client_version(ctx.obj, '--ro_update', 'v1')
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname == 'osmclient.sol005.client.Client':
+ resp = ctx.obj.vim.list(filter)
+ else:
+ resp = ctx.obj.vim.list(ro_update)
table = PrettyTable(['vim name', 'uuid'])
for vim in resp:
table.add_row([vim['name'], vim['uuid']])
@click.argument('name')
@click.pass_context
def vim_show(ctx, name):
+ '''shows the details of a VIM account
+
+ NAME: name or ID of the VIM account
+ '''
try:
resp = ctx.obj.vim.get(name)
except ClientException as inst:
print(table)
+####################
+# Other operations
+####################
+
+@cli.command(name='upload-package')
+@click.argument('filename')
+@click.pass_context
+def upload_package(ctx, filename):
+ '''uploads a VNF package or NS package
+
+ FILENAME: VNF or NS package file (tar.gz)
+ '''
+ try:
+ ctx.obj.package.upload(filename)
+ fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
+ if fullclassname != 'osmclient.sol005.client.Client':
+ ctx.obj.package.wait_for_upload(filename)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
+
+@cli.command(name='ns-scaling-show')
+@click.argument('ns_name')
+@click.pass_context
+def show_ns_scaling(ctx, ns_name):
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ resp = ctx.obj.ns.list()
+
+ table = PrettyTable(
+ ['group-name',
+ 'instance-id',
+ 'operational status',
+ 'create-time',
+ 'vnfr ids'])
+
+ for ns in resp:
+ if ns_name == ns['name']:
+ nsopdata = ctx.obj.ns.get_opdata(ns['id'])
+ scaling_records = nsopdata['nsr:nsr']['scaling-group-record']
+ for record in scaling_records:
+ if 'instance' in record:
+ instances = record['instance']
+ for inst in instances:
+ table.add_row(
+ [record['scaling-group-name-ref'],
+ inst['instance-id'],
+ inst['op-status'],
+ time.strftime('%Y-%m-%d %H:%M:%S',
+ time.localtime(
+ inst['create-time'])),
+ inst['vnfrs']])
+ table.align = 'l'
+ print(table)
+
+
+@cli.command(name='ns-scale')
+@click.argument('ns_name')
+@click.option('--ns_scale_group', prompt=True)
+@click.option('--index', prompt=True)
+@click.pass_context
+def ns_scale(ctx, ns_name, ns_scale_group, index):
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ ctx.obj.ns.scale(ns_name, ns_scale_group, index)
+
+
+@cli.command(name='config-agent-list')
+@click.pass_context
+def config_agent_list(ctx):
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ table = PrettyTable(['name', 'account-type', 'details'])
+ for account in ctx.obj.vca.list():
+ table.add_row(
+ [account['name'],
+ account['account-type'],
+ account['juju']])
+ table.align = 'l'
+ print(table)
+
+
+@cli.command(name='config-agent-delete')
+@click.argument('name')
+@click.pass_context
+def config_agent_delete(ctx, name):
+ try:
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ ctx.obj.vca.delete(name)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
+
+@cli.command(name='config-agent-add')
+@click.option('--name',
+ prompt=True)
+@click.option('--account_type',
+ prompt=True)
+@click.option('--server',
+ prompt=True)
+@click.option('--user',
+ prompt=True)
+@click.option('--secret',
+ prompt=True,
+ hide_input=True,
+ confirmation_prompt=True)
+@click.pass_context
+def config_agent_add(ctx, name, account_type, server, user, secret):
+ try:
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
+ ctx.obj.vca.create(name, account_type, server, user, secret)
+ except ClientException as inst:
+ print(inst.message)
+ exit(1)
+
@cli.command(name='ro-dump')
@click.pass_context
def ro_dump(ctx):
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
resp = ctx.obj.vim.get_resource_orchestrator()
table = PrettyTable(['key', 'attribute'])
for k, v in resp.items():
@cli.command(name='vcs-list')
@click.pass_context
def vcs_list(ctx):
+ check_client_version(ctx.obj, ctx.command.name, 'v1')
resp = ctx.obj.utils.get_vcs_info()
table = PrettyTable(['component name', 'state'])
for component in resp:
--- /dev/null
+# 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.
--- /dev/null
+# 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 SOL005 client API
+"""
+
+#from osmclient.v1 import vnf
+#from osmclient.v1 import ns
+#from osmclient.v1 import vim
+#from osmclient.v1 import vca
+from osmclient.sol005 import vnfd
+from osmclient.sol005 import nsd
+from osmclient.sol005 import ns
+from osmclient.sol005 import vim
+from osmclient.sol005 import package
+from osmclient.sol005 import http
+from osmclient.common.exceptions import ClientException
+
+class Client(object):
+
+ def __init__(
+ self,
+ host=None,
+ so_port=9999,
+ so_project='admin',
+ ro_host=None,
+ ro_port=9090,
+ **kwargs):
+
+ self._user = 'admin'
+ self._password = 'admin'
+ #self._project = so_project
+ self._project = 'admin'
+ self._auth_endpoint = '/admin/v1/tokens'
+ self._headers = {}
+
+ if len(host.split(':')) > 1:
+ # backwards compatible, port provided as part of host
+ self._host = host.split(':')[0]
+ self._so_port = host.split(':')[1]
+ else:
+ self._host = host
+ self._so_port = so_port
+
+ if ro_host is None:
+ ro_host = host
+ ro_http_client = http.Http('http://{}:{}/'.format(ro_host, ro_port))
+ ro_http_client.set_http_header(
+ ['Accept: application/vnd.yand.data+json',
+ 'Content-Type: application/json'])
+
+ self._http_client = http.Http(
+ 'https://{}:{}/osm'.format(self._host,self._so_port))
+ self._headers['Accept'] = 'application/json'
+ self._headers['Content-Type'] = 'application/yaml'
+ http_header = ['{}: {}'.format(key,val)
+ for (key,val) in self._headers.items()]
+ self._http_client.set_http_header(http_header)
+
+ token = self.get_token()
+ if not token:
+ raise ClientException(
+ 'Authentication error: not possible to get auth token')
+ self._headers['Authorization'] = 'Bearer {}'.format(token)
+ http_header.append('Authorization: Bearer {}'.format(token))
+ self._http_client.set_http_header(http_header)
+
+ self.vnfd = vnfd.Vnfd(self._http_client, client=self)
+ self.nsd = nsd.Nsd(self._http_client, client=self)
+ self.package = package.Package(self._http_client, client=self)
+ self.ns = ns.Ns(self._http_client, client=self)
+ self.vim = vim.Vim(self._http_client, client=self)
+ '''
+ self.vnf = vnf.Vnf(http_client, client=self, **kwargs)
+ self.vca = vca.Vca(http_client, client=self, **kwargs)
+ self.utils = utils.Utils(http_client, **kwargs)
+ '''
+
+ def get_token(self):
+ postfields_dict = {'username': self._user,
+ 'password': self._password,
+ 'project-id': self._project}
+ token = self._http_client.post_cmd(endpoint=self._auth_endpoint,
+ postfields_dict=postfields_dict)
+ if token is not None:
+ return token['_id']
+ return None
+
--- /dev/null
+# 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.
+
+from io import BytesIO
+import pycurl
+import json
+import yaml
+from osmclient.common import http
+from osmclient.common.exceptions import ClientException
+
+class Http(http.Http):
+
+ def __init__(self, url, user='admin', password='admin'):
+ self._url = url
+ self._user = user
+ self._password = password
+ self._http_header = None
+
+ def _get_curl_cmd(self, endpoint):
+ curl_cmd = pycurl.Curl()
+ #print self._url + endpoint
+ curl_cmd.setopt(pycurl.URL, self._url + endpoint)
+ curl_cmd.setopt(pycurl.SSL_VERIFYPEER, 0)
+ curl_cmd.setopt(pycurl.SSL_VERIFYHOST, 0)
+ if self._http_header:
+ curl_cmd.setopt(pycurl.HTTPHEADER, self._http_header)
+ return curl_cmd
+
+ def delete_cmd(self, endpoint):
+ data = BytesIO()
+ curl_cmd = self._get_curl_cmd(endpoint)
+ curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
+ curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
+ curl_cmd.perform()
+ http_code = curl_cmd.getinfo(pycurl.HTTP_CODE)
+ #print 'HTTP_CODE: {}'.format(http_code)
+ curl_cmd.close()
+ if http_code == 204:
+ return None
+ elif http_code == 404:
+ if data.getvalue():
+ return json.loads(data.getvalue().decode())
+ else:
+ return "NOT FOUND"
+ if data.getvalue():
+ return json.loads(data.getvalue().decode())
+ return "Failed"
+
+ def send_cmd(self, endpoint='', postfields_dict=None,
+ formfile=None, filename=None,
+ put_method=False):
+ data = BytesIO()
+ curl_cmd = self._get_curl_cmd(endpoint)
+ if put_method:
+ curl_cmd.setopt(pycurl.PUT, 1)
+ else:
+ curl_cmd.setopt(pycurl.POST, 1)
+ curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
+
+ if postfields_dict is not None:
+ jsondata = json.dumps(postfields_dict)
+ curl_cmd.setopt(pycurl.POSTFIELDS, jsondata)
+ elif formfile is not None:
+ curl_cmd.setopt(
+ pycurl.HTTPPOST,
+ [((formfile[0],
+ (pycurl.FORM_FILE,
+ formfile[1])))])
+ elif filename is not None:
+ with open(filename, 'r') as stream:
+ postdata=stream.read()
+ curl_cmd.setopt(pycurl.POSTFIELDS, postdata)
+
+ curl_cmd.perform()
+ http_code = curl_cmd.getinfo(pycurl.HTTP_CODE)
+ curl_cmd.close()
+ if http_code not in (200, 201, 202, 204):
+ raise ClientException(data.getvalue().decode())
+ if postfields_dict is not None:
+ if data.getvalue():
+ return json.loads(data.getvalue().decode())
+ return None
+ elif formfile is not None:
+ if data.getvalue():
+ return yaml.safe_load(data.getvalue().decode())
+ return None
+ elif filename is not None:
+ if data.getvalue():
+ return yaml.safe_load(data.getvalue().decode())
+ return None
+ return None
+
+ def post_cmd(self, endpoint='', postfields_dict=None,
+ formfile=None, filename=None):
+ return self.send_cmd(endpoint=endpoint,
+ postfields_dict=postfields_dict,
+ formfile=formfile,
+ filename=filename,
+ put_method=False)
+
+ def put_cmd(self, endpoint='', postfields_dict=None,
+ formfile=None, filename=None):
+ return self.send_cmd(endpoint=endpoint,
+ postfields_dict=postfields_dict,
+ formfile=formfile,
+ filename=filename,
+ put_method=True)
+
+ def get2_cmd(self, endpoint):
+ data = BytesIO()
+ curl_cmd = self._get_curl_cmd(endpoint)
+ curl_cmd.setopt(pycurl.HTTPGET, 1)
+ curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
+ curl_cmd.perform()
+ curl_cmd.close()
+ return data.getvalue()
+
--- /dev/null
+# 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 ns API handling
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import yaml
+
+
+class Ns(object):
+
+ def __init__(self, http=None, client=None):
+ self._http = http
+ self._client = client
+ self._apiName = '/nslcm'
+ self._apiVersion = '/v1'
+ self._apiResource = '/ns_instances_content'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+
+ def list(self, filter=None):
+ """Returns a list of NS
+ """
+ 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 NS based on name or id
+ """
+ if utils.validate_uuid4(name):
+ for ns in self.list():
+ if name == ns['_id']:
+ return ns
+ else:
+ for ns in self.list():
+ if name == ns['name']:
+ return ns
+ raise NotFound("ns {} not found".format(name))
+
+ def get_individual(self, name):
+ ns_id = name
+ if not utils.validate_uuid4(name):
+ for ns in self.list():
+ if name == ns['name']:
+ ns_id = ns['_id']
+ break
+ resp = self._http.get_cmd('{}/{}'.format(self._apiBase, ns_id))
+ #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
+ #print yaml.safe_dump(resp)
+ if resp:
+ return resp
+ raise NotFound("ns {} not found".format(name))
+
+ def delete(self, name):
+ ns = self.get(name)
+ resp = self._http.delete_cmd('{}/{}'.format(self._apiBase,ns['_id']))
+ #print 'RESP: '.format(resp)
+ if resp is None:
+ print 'Deleted'
+ else:
+ raise ClientException("failed to delete ns {}: {}".format(name, resp))
+
+ def create(self, nsd_name, nsr_name, account, config=None,
+ ssh_keys=None, description='default description',
+ admin_status='ENABLED'):
+
+ nsd = self._client.nsd.get(nsd_name)
+
+ datacenter = self._client.vim.get(account)
+ if datacenter is None:
+ raise NotFound("cannot find datacenter account {}".format(account))
+
+ ns = {}
+ ns['nsdId'] = nsd['_id']
+ ns['nsName'] = nsr_name
+ ns['nsDescription'] = description
+ #ns['vimAccountId'] = datacenter['_id']
+ # TODO: Fix it to use datacenter _id
+ ns['vimAccountId'] = datacenter['name']
+ #ns['userdata'] = {}
+ #ns['userdata']['key1']='value1'
+ #ns['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
+
+ #print yaml.safe_dump(ns)
+ try:
+ self._apiResource = '/ns_instances_content'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ #print resp
+ resp = self._http.post_cmd(endpoint=self._apiBase,
+ postfields_dict=ns)
+ if not resp or 'id' not in resp:
+ raise ClientException('unexpected response from server: '.format(
+ resp))
+ else:
+ print resp['id']
+ except ClientException as exc:
+ message="failed to create ns: {} nsd: {}\nerror:\n{}".format(
+ nsr_name,
+ nsd_name,
+ exc.message)
+ raise ClientException(message)
+
--- /dev/null
+# 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 nsd API handling
+"""
+
+from osmclient.common.exceptions import NotFound
+from osmclient.common.exceptions import ClientException
+from osmclient.common import utils
+import yaml
+import magic
+#from os import stat
+#from os.path import basename
+
+class Nsd(object):
+
+ def __init__(self, http=None, client=None):
+ self._http = http
+ self._client = client
+ self._apiName = '/nsd'
+ self._apiVersion = '/v1'
+ self._apiResource = '/ns_descriptors'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ #self._apiBase='/nsds'
+
+ 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 nsd in self.list():
+ if name == nsd['_id']:
+ return nsd
+ else:
+ for nsd in self.list():
+ if 'name' in nsd and name == nsd['name']:
+ return nsd
+ raise NotFound("nsd {} not found".format(name))
+
+ def get_individual(self, name):
+ nsd = self.get(name)
+ # It is redundant, since the previous one already gets the whole nsdinfo
+ # The only difference is that a different primitive is exercised
+ resp = self._http.get_cmd('{}/{}'.format(self._apiBase, nsd['_id']))
+ #print yaml.safe_dump(resp)
+ if resp:
+ return resp
+ raise NotFound("nsd {} not found".format(name))
+
+ def get_thing(self, name, thing, filename):
+ nsd = self.get(name)
+ headers = self._client._headers
+ headers['Accept'] = 'application/binary'
+ resp2 = self._http.get2_cmd('{}/{}/{}'.format(self._apiBase, nsd['_id'], thing))
+ #print yaml.safe_dump(resp2)
+ if resp2:
+ #store in a file
+ return resp2
+ raise NotFound("nsd {} not found".format(name))
+
+ def get_descriptor(self, name, filename):
+ self.get_thing(name, 'nsd', filename)
+
+ def get_package(self, name, filename):
+ self.get_thing(name, 'package_content', filename)
+
+ def get_artifact(self, name, artifact, filename):
+ self.get_thing(name, 'artifacts/{}'.format(artifact), filename)
+
+ def delete(self, name):
+ nsd = self.get(name)
+ resp = self._http.delete_cmd('{}/{}'.format(self._apiBase, nsd['_id']))
+ #print 'RESP: '.format(resp)
+ if resp is None:
+ print 'Deleted'
+ else:
+ raise ClientException("failed to delete nsd {}: {}".format(name, resp))
+
+ 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 == 'application/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 headers.items()]
+ self._http.set_http_header(http_header)
+ if update_endpoint:
+ resp = self._http.put_cmd(endpoint=update_endpoint, filename=filename)
+ else:
+ ow_string = ''
+ if overwrite:
+ ow_string = '?{}'.format(overwrite)
+ self._apiResource = '/ns_descriptors_content'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ endpoint = '{}{}'.format(self._apiBase,ow_string)
+ resp = self._http.post_cmd(endpoint=endpoint, filename=filename)
+ #print resp
+ if not resp or 'id' not in resp:
+ raise ClientException("failed to upload package")
+ else:
+ print resp['id']
+
+ def update(self, name, filename):
+ nsd = self.get(name)
+ endpoint = '{}/{}/nsd_content'.format(self._apiBase, nsd['_id'])
+ self.create(filename=filename, update_endpoint=endpoint)
+
--- /dev/null
+# 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 package API handling
+"""
+
+import tarfile
+import re
+import yaml
+#from os import stat
+#from os.path import basename
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+from osmclient.common import utils
+
+
+class Package(object):
+ def __init__(self, http=None, client=None):
+ self._client = client
+ self._http = http
+
+ def get_key_val_from_pkg(self, descriptor_file):
+ utils.get_key_val_from_pkg(descriptor_file)
+
+ def upload(self, filename):
+ pkg_type = utils.get_key_val_from_pkg(filename)
+ if pkg_type is None:
+ raise ClientException("Cannot determine package type")
+ if pkg_type['type'] == 'nsd':
+ endpoint = '/nsd/v1/ns_descriptors'
+ else:
+ endpoint = '/vnfpkgm/v1/vnf_packages'
+ #endpoint = '/nsds' if pkg_type['type'] == 'nsd' else '/vnfds'
+ #print 'Endpoint: {}'.format(endpoint)
+ headers = self._client._headers
+ 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)
+ headers["Content-File-MD5"] = utils.md5(filename)
+ http_header = ['{}: {}'.format(key,val)
+ for (key,val) in headers.items()]
+ self._http.set_http_header(http_header)
+ resp = self._http.post_cmd(endpoint=endpoint,
+ filename=filename)
+ #print 'RESP: {}'.format(yaml.safe_dump(resp))
+ if not resp or 'id' not in resp:
+ raise ClientException("failed to upload package")
+ else:
+ print resp['id']
+
+
--- /dev/null
+# 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 SDN controller API handling
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import yaml
+
+
+class SdnController(object):
+ def __init__(self, http=None, client=None):
+ self._http = http
+ self._client = client
+ self._apiName = '/admin'
+ self._apiVersion = '/v1'
+ self._apiResource = '/sdn_controllers'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ def create(self, name, sdn_controller):
+ if 'type' not in vim_access:
+ raise Exception("type not provided")
+
+ resp = self._http.post_cmd(endpoint=self._apiBase,
+ postfields_dict=sdn_controller)
+ if not resp or '_id' not in resp:
+ raise ClientException('failed to create SDN controller: '.format(
+ resp))
+ else:
+ print resp['_id']
+
+ def delete(self, name):
+ sdn_controller = self.get(name)
+ resp = self._http.delete_cmd('{}/{}'.format(self._apiBase,sdn_controller['_id']))
+ #print 'RESP: '.format(resp)
+ if 'result' not in resp:
+ raise ClientException("failed to delete vim {} - {}".format(name, resp))
+ else:
+ print 'Deleted'
+
+ def list(self, filter=None):
+ """Returns a list of SDN controllers
+ """
+ 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 SDN controller based on name or id
+ """
+ if utils.validate_uuid4(name):
+ for sdnc in self.list():
+ if name == sdnc['_id']:
+ return sdnc
+ else:
+ for sdnc in self.list():
+ if name == sdnc['name']:
+ return sdnc
+ raise NotFound("SDN controller {} not found".format(name))
+
+
--- /dev/null
+# 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 vim API handling
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import yaml
+
+
+class Vim(object):
+ def __init__(self, http=None, client=None):
+ self._http = http
+ self._client = client
+ self._apiName = '/admin'
+ self._apiVersion = '/v1'
+ self._apiResource = '/vims'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ def create(self, name, vim_access):
+ if 'vim-type' not in vim_access:
+ #'openstack' not in vim_access['vim-type']):
+ raise Exception("vim type not provided")
+
+ vim_account = {}
+ vim_config = {'hello': 'hello'}
+ vim_account['name'] = name
+ vim_account = self.update_vim_account_dict(vim_account, vim_access)
+
+ vim_config = {}
+ if 'config' in vim_access and vim_access['config'] is not None:
+ vim_config = yaml.load(vim_access['config'])
+
+ vim_account['config'] = vim_config
+
+ resp = self._http.post_cmd(endpoint=self._apiBase,
+ postfields_dict=vim_account)
+ if not resp or 'id' not in resp:
+ raise ClientException('failed to create vim {}: {}'.format(
+ name, resp))
+ else:
+ print resp['id']
+
+ def update(self, vim_name, vim_account):
+ vim = self.get(vim_name)
+ resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,vim['_id']),
+ postfields_dict=vim_account)
+ if not resp or '_id' not in resp:
+ raise ClientException('failed to update vim: '.format(
+ resp))
+ else:
+ print resp['_id']
+
+ def update_vim_account_dict(self, vim_account, vim_access):
+ vim_account['vim_type'] = vim_access['vim-type']
+ vim_account['description'] = vim_access['description']
+ vim_account['vim_url'] = vim_access['vim-url']
+ vim_account['vim_user'] = vim_access['vim-username']
+ vim_account['vim_password'] = vim_access['vim-password']
+ vim_account['vim_tenant_name'] = vim_access['vim-tenant-name']
+ return vim_account
+
+ def get_id(self, name):
+ """Returns a VIM id from a VIM name
+ """
+ for vim in self.list():
+ if name == vim['name']:
+ return vim['uuid']
+ raise NotFound("vim {} not found".format(name))
+
+ def delete(self, vim_name):
+ vim_id = vim_name
+ if not utils.validate_uuid4(vim_name):
+ vim_id = self.get_id(vim_name)
+ resp = self._http.delete_cmd('{}/{}'.format(self._apiBase,vim_id))
+ if resp is None:
+ print 'Deleted'
+ else:
+ raise ClientException("failed to delete vim {} - {}".format(vim_name, resp))
+
+ def list(self, filter=None):
+ """Returns a list of VIM accounts
+ """
+ filter_string = ''
+ if filter:
+ filter_string = '?{}'.format(filter)
+ resp = self._http.get_cmd('{}{}'.format(self._apiBase,filter_string))
+ if not resp:
+ return list()
+ vim_accounts = []
+ for datacenter in resp:
+ vim_accounts.append({"name": datacenter['name'], "uuid": datacenter['_id']
+ if '_id' in datacenter else None})
+ return vim_accounts
+
+ def get(self, name):
+ """Returns a VIM account based on name or id
+ """
+ vim_id = name
+ if not utils.validate_uuid4(name):
+ vim_id = self.get_id(name)
+ resp = self._http.get_cmd('{}/{}'.format(self._apiBase,vim_id))
+ if not resp or '_id' not in resp:
+ raise ClientException('failed to get vim info: '.format(
+ resp))
+ else:
+ return resp
+ raise NotFound("vim {} not found".format(name))
+
--- /dev/null
+# 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 vnfd API handling
+"""
+
+from osmclient.common.exceptions import NotFound
+from osmclient.common.exceptions import ClientException
+from osmclient.common import utils
+import yaml
+import magic
+#from os import stat
+#from os.path import basename
+
+class Vnfd(object):
+
+ def __init__(self, http=None, client=None):
+ self._http = http
+ self._client = client
+ self._apiName = '/vnfpkgm'
+ self._apiVersion = '/v1'
+ self._apiResource = '/vnf_packages'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ #self._apiBase='/vnfds'
+
+ def list(self, filter=None):
+ 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):
+ if utils.validate_uuid4(name):
+ for vnfd in self.list():
+ if name == vnfd['_id']:
+ return vnfd
+ else:
+ for vnfd in self.list():
+ if 'name' in vnfd and name == vnfd['name']:
+ return vnfd
+ raise NotFound("vnfd {} not found".format(name))
+
+ def get_individual(self, name):
+ vnfd = self.get(name)
+ # It is redundant, since the previous one already gets the whole vnfpkginfo
+ # The only difference is that a different primitive is exercised
+ resp = self._http.get_cmd('{}/{}'.format(self._apiBase, vnfd['_id']))
+ #print yaml.safe_dump(resp)
+ if resp:
+ return resp
+ raise NotFound("vnfd {} not found".format(name))
+
+ def get_thing(self, name, thing, filename):
+ vnfd = self.get(name)
+ headers = self._client._headers
+ headers['Accept'] = 'application/binary'
+ resp2 = self._http.get2_cmd('{}/{}/{}'.format(self._apiBase, vnfd['_id'], thing))
+ #print yaml.safe_dump(resp2)
+ if resp2:
+ #store in a file
+ return resp2
+ raise NotFound("vnfd {} not found".format(name))
+
+ def get_descriptor(self, name, filename):
+ self.get_thing(name, 'vnfd', filename)
+
+ def get_package(self, name, filename):
+ self.get_thing(name, 'package_content', filename)
+
+ def get_artifact(self, name, artifact, filename):
+ self.get_thing(name, 'artifacts/{}'.format(artifact), filename)
+
+ def delete(self, name):
+ vnfd = self.get(name)
+ resp = self._http.delete_cmd('{}/{}'.format(self._apiBase,vnfd['_id']))
+ #print 'RESP: '.format(resp)
+ if resp is None:
+ print 'Deleted'
+ else:
+ raise ClientException("failed to delete vnfd {}: {}".format(name, resp))
+
+ 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 == 'application/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 headers.items()]
+ self._http.set_http_header(http_header)
+ if update_endpoint:
+ resp = self._http.put_cmd(endpoint=update_endpoint, filename=filename)
+ else:
+ ow_string = ''
+ if overwrite:
+ ow_string = '?{}'.format(overwrite)
+ self._apiResource = '/vnf_packages_content'
+ self._apiBase = '{}{}{}'.format(self._apiName,
+ self._apiVersion, self._apiResource)
+ endpoint = '{}{}'.format(self._apiBase,ow_string)
+ resp = self._http.post_cmd(endpoint=endpoint, filename=filename)
+ #print resp
+ if not resp or 'id' not in resp:
+ raise ClientException("failed to upload package")
+ else:
+ print resp['id']
+
+ def update(self, name, filename):
+ vnfd = self.get(name)
+ endpoint = '{}/{}/vnfd_content'.format(self._apiBase, vnfd['_id'])
+ self.create(filename=filename, update_endpoint=endpoint)
+
OSM package API handling
"""
-import tarfile
-import re
-import yaml
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
from osmclient.common import utils
check_exists(lambda:
get_method(pkg_type['name'])))
- # method opens up a package and finds the name of the resulting
- # descriptor (vnfd or nsd name)
def get_key_val_from_pkg(self, descriptor_file):
- tar = tarfile.open(descriptor_file)
- yamlfile = None
- for member in tar.getmembers():
- if (re.match('.*.yaml', member.name) and
- len(member.name.split('/')) == 2):
- yamlfile = member.name
- break
- if yamlfile is None:
- return None
-
- dict = yaml.load(tar.extractfile(yamlfile))
- result = {}
- for k1, v1 in dict.items():
- if not k1.endswith('-catalog'):
- continue
- for k2, v2 in v1.items():
- if not k2.endswith('nsd') and not k2.endswith('vnfd'):
- continue
-
- if 'nsd' in k2:
- result['type'] = 'nsd'
- else:
- result['type'] = 'vnfd'
-
- for entry in v2:
- for k3, v3 in entry.items():
- # strip off preceeding chars before :
- key_name = k3.split(':').pop()
-
- result[key_name] = v3
- tar.close()
- return result
+ utils.get_key_val_from_pkg(descriptor_file)
def wait_for_upload(self, filename):
"""wait(block) for an upload to succeed.
The filename passed is assumed to be a descriptor tarball.
"""
- pkg_type = self.get_key_val_from_pkg(filename)
+ pkg_type = utils.get_key_val_from_pkg(filename)
if pkg_type is None:
raise ClientException("Cannot determine package type")
packages=find_packages(),
include_package_data=True,
install_requires=[
- 'Click', 'prettytable', 'pyyaml', 'pycurl'
+ 'Click', 'prettytable', 'pyyaml', 'pycurl', 'python-magic'
],
setup_requires=['setuptools-version-command'],
test_suite='nose.collector',