--- /dev/null
+# Copyright 2019 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 API handling for the '--wait' option
+"""
+
+from osmclient.common.exceptions import ClientException
+import json
+from time import sleep
+import sys
+
+# Declare a constant for each module, to allow customizing each timeout in the future
+TIMEOUT_GENERIC_OPERATION = 600
+TIMEOUT_NSI_OPERATION = TIMEOUT_GENERIC_OPERATION
+TIMEOUT_SDNC_OPERATION = TIMEOUT_GENERIC_OPERATION
+TIMEOUT_VIM_OPERATION = TIMEOUT_GENERIC_OPERATION
+TIMEOUT_WIM_OPERATION = TIMEOUT_GENERIC_OPERATION
+TIMEOUT_NS_OPERATION = 3600
+
+POLLING_TIME_INTERVAL = 1
+
+MAX_DELETE_ATTEMPTS = 3
+
+def _show_detailed_status(old_detailed_status, new_detailed_status):
+ if new_detailed_status is not None and new_detailed_status != old_detailed_status:
+ sys.stderr.write("detailed-status: {}\n".format(new_detailed_status))
+ return new_detailed_status
+ else:
+ return old_detailed_status
+
+def _get_finished_states(entity):
+ # Note that the member name is either:
+ # 'operationState' (NS and NSI)
+ # '_admin.'operationalState' (other)
+ # For NS and NSI, 'operationState' may be one of:
+ # PROCESSING, COMPLETED,PARTIALLY_COMPLETED, FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
+ # For other entities, '_admin.operationalState' may be one of:
+ # operationalState: ENABLED, DISABLED, ERROR, PROCESSING
+ if entity == 'NS' or entity == 'NSI':
+ return ['COMPLETED', 'PARTIALLY_COMPLETED', 'FAILED_TEMP', 'FAILED']
+ else:
+ return ['ENABLED', 'ERROR']
+
+def _get_operational_state(resp, entity):
+ # Note that the member name is either:
+ # 'operationState' (NS)
+ # 'operational-status' (NSI)
+ # '_admin.'operationalState' (other)
+ if entity == 'NS' or entity == 'NSI':
+ return resp.get('operationState')
+ else:
+ return resp.get('_admin', {}).get('operationalState')
+
+def _op_has_finished(resp, entity):
+ # _op_has_finished() returns:
+ # 0 on success (operation has finished)
+ # 1 on pending (operation has not finished)
+ # -1 on error (bad response)
+ #
+ finished_states = _get_finished_states(entity)
+ if resp:
+ operationalState = _get_operational_state(resp, entity)
+ if operationalState:
+ if operationalState in finished_states:
+ return 0
+ return 1
+ return -1
+
+def _get_detailed_status(resp, entity, detailed_status_deleted):
+ if detailed_status_deleted:
+ return detailed_status_deleted
+ if entity == 'NS' or entity == 'NSI':
+ # For NS and NSI, 'detailed-status' is a JSON "root" member:
+ return resp.get('detailed-status')
+ else:
+ # For other entities, 'detailed-status' a leaf node to '_admin':
+ return resp.get('_admin', {}).get('detailed-status')
+
+def _has_delete_error(resp, entity, deleteFlag, delete_attempts_left):
+ if deleteFlag and delete_attempts_left:
+ state = _get_operational_state(resp, entity)
+ if state and state == 'ERROR':
+ return True
+ return False
+
+def wait_for_status(entity_label, entity_id, timeout, apiUrlStatus, http_cmd, deleteFlag=False):
+ # Arguments:
+ # entity_label: String describing the entities using '--wait':
+ # 'NS', 'NSI', 'SDNC', 'VIM', 'WIM'
+ # entity_id: The ID for an existing entity, the operation ID for an entity to create.
+ # timeout: See section at top of this file for each value of TIMEOUT_<ENTITY>_OPERATION
+ # apiUrlStatus: The endpoint to get the Response including 'detailed-status'
+ # http_cmd: callback to HTTP command.
+ # Passing this callback as an argument avoids importing the 'http' module here.
+
+ # Loop here until the operation finishes, or a timeout occurs.
+ time_left = timeout
+ detailed_status = None
+ detailed_status_deleted = None
+ time_to_return = False
+ delete_attempts_left = MAX_DELETE_ATTEMPTS
+ try:
+ while True:
+ http_code, resp_unicode = http_cmd('{}/{}'.format(apiUrlStatus, entity_id))
+ resp = ''
+ if resp_unicode:
+ resp = json.loads(resp_unicode)
+ # print 'HTTP CODE: {}'.format(http_code)
+ # print 'RESP: {}'.format(resp)
+ # print 'URL: {}/{}'.format(apiUrlStatus, entity_id)
+ if deleteFlag and http_code == 404:
+ # In case of deletion, '404 Not Found' means successfully deleted
+ # Display 'detailed-status: Deleted' and return
+ time_to_return = True
+ detailed_status_deleted = 'Deleted'
+ elif http_code not in (200, 201, 202, 204):
+ raise ClientException(str(resp))
+ if not time_to_return:
+ # Get operation status
+ op_status = _op_has_finished(resp, entity_label)
+ if op_status == -1:
+ # An error occurred
+ raise ClientException('unexpected response from server - {} '.format(
+ str(resp)))
+ elif op_status == 0:
+ # If there was an error upon deletion, try again to delete the same instance
+ # If the error is the same, there is probably nothing we can do but exit with error.
+ # If the error is different (i.e. 404), the instance was probably already corrupt, that is,
+ # operation(al)State was probably ERROR before deletion.
+ # In such a case, even if the previous state was ERROR, the deletion was successful,
+ # so detailed-status should be set to Deleted.
+ if _has_delete_error(resp, entity_label, deleteFlag, delete_attempts_left):
+ delete_attempts_left -= 1
+ else:
+ # Operation has finished, either with success or error
+ if deleteFlag:
+ if delete_attempts_left < MAX_DELETE_ATTEMPTS:
+ time_to_return = True
+ delete_attempts_left -= 1
+ else:
+ time_to_return = True
+ new_detailed_status = _get_detailed_status(resp, entity_label, detailed_status_deleted)
+ if not new_detailed_status:
+ new_detailed_status = 'In progress'
+ # TODO: Change LCM to provide detailed-status more up to date
+ # At the moment of this writing, 'detailed-status' may return different strings
+ # from different resources:
+ # /nslcm/v1/ns_lcm_op_occs/<id> ---> ''
+ # /nslcm/v1/ns_instances_content/<id> ---> 'deleting charms'
+ detailed_status = _show_detailed_status(detailed_status, new_detailed_status)
+ if time_to_return:
+ return
+ time_left -= POLLING_TIME_INTERVAL
+ sleep(POLLING_TIME_INTERVAL)
+ if time_left <= 0:
+ # There was a timeout, so raise an exception
+ raise ClientException('operation timeout, waited for {} seconds'.format(timeout))
+ except ClientException as exc:
+ message="Operation failed for {}:\nerror:\n{}".format(
+ entity_label,
+ exc.message)
+ raise ClientException(message)
@click.option('--config_file',
default=None,
help='ns specific yaml configuration file')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def ns_create(ctx,
nsd_name,
admin_status,
ssh_keys,
config,
- config_file):
+ config_file,
+ wait):
"""creates a new NS instance"""
try:
if config_file:
ns_name,
config=config,
ssh_keys=ssh_keys,
- account=vim_account)
+ account=vim_account,
+ wait=wait)
except ClientException as inst:
print(inst.message)
exit(1)
nst_create(ctx, filename, overwrite)
-def nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file):
+def nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait):
"""creates a new Network Slice Instance (NSI)"""
try:
check_client_version(ctx.obj, ctx.command.name)
with open(config_file, 'r') as cf:
config=cf.read()
ctx.obj.nsi.create(nst_name, nsi_name, config=config, ssh_keys=ssh_keys,
- account=vim_account)
+ account=vim_account, wait=wait)
except ClientException as inst:
print(inst.message)
exit(1)
@click.option('--config_file',
default=None,
help='nsi specific yaml configuration file')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def nsi_create1(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file):
+def nsi_create1(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait):
"""creates a new Network Slice Instance (NSI)"""
- nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file)
+ nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait)
@cli.command(name='netslice-instance-create', short_help='creates a new Network Slice Instance')
@click.option('--config_file',
default=None,
help='nsi specific yaml configuration file')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def nsi_create2(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file):
+def nsi_create2(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait):
"""creates a new Network Slice Instance (NSI)"""
- nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file)
+ nsi_create(ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait)
@cli.command(name='pdu-create', short_help='adds a new Physical Deployment Unit to the catalog')
@cli.command(name='ns-delete', short_help='deletes a NS instance')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def ns_delete(ctx, name, force):
+def ns_delete(ctx, name, force, wait):
"""deletes a NS instance
NAME: name or ID of the NS instance to be deleted
"""
try:
if not force:
- ctx.obj.ns.delete(name)
+ ctx.obj.ns.delete(name, wait=wait)
else:
check_client_version(ctx.obj, '--force')
- ctx.obj.ns.delete(name, force)
+ ctx.obj.ns.delete(name, force, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
nst_delete(ctx, name, force)
-def nsi_delete(ctx, name, force):
+def nsi_delete(ctx, name, force, wait):
try:
check_client_version(ctx.obj, ctx.command.name)
- ctx.obj.nsi.delete(name, force)
+ ctx.obj.nsi.delete(name, force, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@cli.command(name='nsi-delete', short_help='deletes a Network Slice Instance (NSI)')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def nsi_delete1(ctx, name, force):
+def nsi_delete1(ctx, name, force, wait):
"""deletes a Network Slice Instance (NSI)
NAME: name or ID of the Network Slice instance to be deleted
"""
- nsi_delete(ctx, name, force)
+ nsi_delete(ctx, name, force, wait=wait)
@cli.command(name='netslice-instance-delete', short_help='deletes a Network Slice Instance (NSI)')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
@click.pass_context
-def nsi_delete2(ctx, name, force):
+def nsi_delete2(ctx, name, force, wait):
"""deletes a Network Slice Instance (NSI)
NAME: name or ID of the Network Slice instance to be deleted
"""
- nsi_delete(ctx, name, force)
+ nsi_delete(ctx, name, force, wait=wait)
@cli.command(name='pdu-delete', short_help='deletes a Physical Deployment Unit (PDU)')
help='human readable description')
@click.option('--sdn_controller', default=None, help='Name or id of the SDN controller associated to this VIM account')
@click.option('--sdn_port_mapping', default=None, help="File describing the port mapping between compute nodes' ports and switch ports")
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def vim_create(ctx,
name,
account_type,
description,
sdn_controller,
- sdn_port_mapping):
+ sdn_port_mapping,
+ wait):
"""creates a new VIM account"""
try:
if sdn_controller:
vim['description'] = description
vim['config'] = config
if sdn_controller or sdn_port_mapping:
- ctx.obj.vim.create(name, vim, sdn_controller, sdn_port_mapping)
+ ctx.obj.vim.create(name, vim, sdn_controller, sdn_port_mapping, wait=wait)
else:
- ctx.obj.vim.create(name, vim)
+ ctx.obj.vim.create(name, vim, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@click.option('--description', help='human readable description')
@click.option('--sdn_controller', default=None, help='Name or id of the SDN controller associated to this VIM account')
@click.option('--sdn_port_mapping', default=None, help="File describing the port mapping between compute nodes' ports and switch ports")
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def vim_update(ctx,
name,
account_type,
description,
sdn_controller,
- sdn_port_mapping):
+ sdn_port_mapping,
+ wait):
"""updates a VIM account
NAME: name or ID of the VIM account
if account_type: vim['vim_type'] = account_type
if description: vim['description'] = description
if config: vim['config'] = config
- ctx.obj.vim.update(name, vim, sdn_controller, sdn_port_mapping)
+ ctx.obj.vim.update(name, vim, sdn_controller, sdn_port_mapping, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@cli.command(name='vim-delete')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def vim_delete(ctx, name, force):
+def vim_delete(ctx, name, force, wait):
"""deletes a VIM account
NAME: name or ID of the VIM account to be deleted
"""
try:
if not force:
- ctx.obj.vim.delete(name)
+ ctx.obj.vim.delete(name, wait=wait)
else:
check_client_version(ctx.obj, '--force')
- ctx.obj.vim.delete(name, force)
+ ctx.obj.vim.delete(name, force, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
default='no description',
help='human readable description')
@click.option('--wim_port_mapping', default=None, help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge (WAN service endpoint id and info)")
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def wim_create(ctx,
name,
config,
wim_type,
description,
- wim_port_mapping):
+ wim_port_mapping,
+ wait):
"""creates a new WIM account"""
try:
check_client_version(ctx.obj, ctx.command.name)
wim['wim_type'] = wim_type
if description: wim['description'] = description
if config: wim['config'] = config
- ctx.obj.wim.create(name, wim, wim_port_mapping)
+ ctx.obj.wim.create(name, wim, wim_port_mapping, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@click.option('--wim_type', help='WIM type')
@click.option('--description', help='human readable description')
@click.option('--wim_port_mapping', default=None, help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge (WAN service endpoint id and info)")
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def wim_update(ctx,
name,
config,
wim_type,
description,
- wim_port_mapping):
+ wim_port_mapping,
+ wait):
"""updates a WIM account
NAME: name or ID of the WIM account
if wim_type: wim['wim_type'] = wim_type
if description: wim['description'] = description
if config: wim['config'] = config
- ctx.obj.wim.update(name, wim, wim_port_mapping)
+ ctx.obj.wim.update(name, wim, wim_port_mapping, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@cli.command(name='wim-delete')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def wim_delete(ctx, name, force):
+def wim_delete(ctx, name, force, wait):
"""deletes a WIM account
NAME: name or ID of the WIM account to be deleted
"""
try:
check_client_version(ctx.obj, ctx.command.name)
- ctx.obj.wim.delete(name, force)
+ ctx.obj.wim.delete(name, force, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
prompt=True,
help='SDN controller type')
@click.option('--sdn_controller_version',
- help='SDN controller username')
+ help='SDN controller version')
@click.option('--ip_address',
prompt=True,
help='SDN controller IP address')
#@click.option('--description',
# default='no description',
# help='human readable description')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def sdnc_create(ctx,
- name,
- type,
- sdn_controller_version,
- ip_address,
- port,
- switch_dpid,
- user,
- password):
+ name,
+ type,
+ sdn_controller_version,
+ ip_address,
+ port,
+ switch_dpid,
+ user,
+ password,
+ wait):
"""creates a new SDN controller"""
sdncontroller = {}
sdncontroller['name'] = name
# sdncontroller['description'] = description
try:
check_client_version(ctx.obj, ctx.command.name)
- ctx.obj.sdnc.create(name, sdncontroller)
+ ctx.obj.sdnc.create(name, sdncontroller, wait=wait)
except ClientException as inst:
print((inst.message))
@click.option('--user', help='SDN controller username')
@click.option('--password', help='SDN controller password')
#@click.option('--description', default=None, help='human readable description')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def sdnc_update(ctx,
- name,
- newname,
- type,
- sdn_controller_version,
- ip_address,
- port,
- switch_dpid,
- user,
- password):
+ name,
+ newname,
+ type,
+ sdn_controller_version,
+ ip_address,
+ port,
+ switch_dpid,
+ user,
+ password,
+ wait):
"""updates an SDN controller
NAME: name or ID of the SDN controller
sdncontroller['password'] = user
try:
check_client_version(ctx.obj, ctx.command.name)
- ctx.obj.sdnc.update(name, sdncontroller)
+ ctx.obj.sdnc.update(name, sdncontroller, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@cli.command(name='sdnc-delete')
@click.argument('name')
@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def sdnc_delete(ctx, name, force):
+def sdnc_delete(ctx, name, force, wait):
"""deletes an SDN controller
NAME: name or ID of the SDN controller to be deleted
"""
try:
check_client_version(ctx.obj, ctx.command.name)
- ctx.obj.sdnc.delete(name, force)
+ ctx.obj.sdnc.delete(name, force, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
#@click.argument('name')
#@click.pass_context
#def ns_alarm_delete(ctx, name):
-# '''deletes an alarm
+# """deletes an alarm
#
# NAME: name of the alarm to be deleted
-# '''
+# """
# try:
# check_client_version(ctx.obj, ctx.command.name)
# ctx.obj.ns.delete_alarm(name)
@click.argument('ns_name')
@click.option('--ns_scale_group', prompt=True)
@click.option('--index', prompt=True)
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
-def ns_scale(ctx, ns_name, ns_scale_group, index):
+def ns_scale(ctx, ns_name, ns_scale_group, index, wait):
"""scales NS
NS_NAME: name of the NS instance to be scaled
"""
try:
check_client_version(ctx.obj, ctx.command.name, 'v1')
- ctx.obj.ns.scale(ns_name, ns_scale_group, index)
+ ctx.obj.ns.scale(ns_name, ns_scale_group, index, wait=wait)
except ClientException as inst:
print((inst.message))
exit(1)
@click.option('--vnf_name', default=None)
@click.option('--action_name', prompt=True)
@click.option('--params', prompt=True)
+@click.option('--wait',
+ required=False,
+ default=False,
+ is_flag=True,
+ help='do not return the control immediately, but keep it \
+ until the operation is completed, or timeout')
@click.pass_context
def ns_action(ctx,
ns_name,
vnf_name,
action_name,
- params):
+ params,
+ wait):
"""executes an action/primitive over a NS instance
NS_NAME: name or ID of the NS instance
op_data['vnf_member_index'] = vnf_name
op_data['primitive'] = action_name
op_data['primitive_params'] = yaml.load(params)
- ctx.obj.ns.exec_op(ns_name, op_name='action', op_data=op_data)
+ ctx.obj.ns.exec_op(ns_name, op_name='action', op_data=op_data, wait=wait)
except ClientException as inst:
print((inst.message))
"""
from osmclient.common import utils
+from osmclient.common import wait as WaitForStatus
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
import yaml
self._apiBase = '{}{}{}'.format(self._apiName,
self._apiVersion, self._apiResource)
+ # NS '--wait' option
+ def _wait(self, id, deleteFlag=False):
+ # Endpoint to get operation status
+ apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/ns_lcm_op_occs')
+ # Wait for status for NS instance creation/update/deletion
+ WaitForStatus.wait_for_status(
+ 'NS',
+ str(id),
+ WaitForStatus.TIMEOUT_NS_OPERATION,
+ apiUrlStatus,
+ self._http.get2_cmd,
+ deleteFlag=deleteFlag)
+
def list(self, filter=None):
"""Returns a list of NS
"""
return resp
raise NotFound("ns {} not found".format(name))
- def delete(self, name, force=False):
+ def delete(self, name, force=False, wait=False):
ns = self.get(name)
querystring = ''
if force:
querystring = '?FORCE=True'
http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
- ns['_id'], querystring))
- #print 'HTTP CODE: {}'.format(http_code)
- #print 'RESP: {}'.format(resp)
+ ns['_id'], querystring))
+ # print 'HTTP CODE: {}'.format(http_code)
+ # print 'RESP: {}'.format(resp)
if http_code == 202:
- print('Deletion in progress')
+ if wait and resp:
+ resp = json.loads(resp)
+ # For the 'delete' operation, '_id' is used
+ self._wait(resp.get('_id'), deleteFlag=True)
+ else:
+ print('Deletion in progress')
elif http_code == 204:
print('Deleted')
else:
def create(self, nsd_name, nsr_name, account, config=None,
ssh_keys=None, description='default description',
- admin_status='ENABLED'):
+ admin_status='ENABLED', wait=False):
nsd = self._client.nsd.get(nsd_name)
self._http.set_http_header(http_header)
http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
postfields_dict=ns)
- #print 'HTTP CODE: {}'.format(http_code)
- #print 'RESP: {}'.format(resp)
+ # 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))
+ if wait:
+ # Wait for status for NS instance creation
+ self._wait(resp.get('nslcmop_id'))
return resp['id']
else:
msg = ""
exc.message)
raise ClientException(message)
- def exec_op(self, name, op_name, op_data=None):
+ def exec_op(self, name, op_name, op_data=None, wait=False):
"""Executes an operation on a NS
"""
ns = self.get(name)
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for NS instance action
+ # For the 'action' operation, 'id' is used
+ self._wait(resp.get('id'))
print(resp['id'])
else:
msg = ""
exc.message)
raise ClientException(message)
- def scale_vnf(self, ns_name, vnf_name, scaling_group, scale_in, scale_out):
+ def scale_vnf(self, ns_name, vnf_name, scaling_group, scale_in, scale_out, wait=False):
"""Scales a VNF by adding/removing VDUs
"""
try:
"member-vnf-index": vnf_name,
"scaling-group-descriptor": scaling_group,
}
- self.exec_op(ns_name, op_name='scale', op_data=op_data)
+ self.exec_op(ns_name, op_name='scale', op_data=op_data, wait=wait)
except ClientException as exc:
message="failed to scale vnf {} of ns {}:\nerror:\n{}".format(
vnf_name, ns_name, exc.message)
"""
from osmclient.common import utils
+from osmclient.common import wait as WaitForStatus
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
import yaml
self._apiBase = '{}{}{}'.format(self._apiName,
self._apiVersion, self._apiResource)
+ # NSI '--wait' option
+ def _wait(self, id, deleteFlag=False):
+ # Endpoint to get operation status
+ apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/nsi_lcm_op_occs')
+ # Wait for status for NSI instance creation/update/deletion
+ WaitForStatus.wait_for_status(
+ 'NSI',
+ str(id),
+ WaitForStatus.TIMEOUT_NSI_OPERATION,
+ apiUrlStatus,
+ self._http.get2_cmd,
+ deleteFlag=deleteFlag)
+
def list(self, filter=None):
"""Returns a list of NSI
"""
return resp
raise NotFound("nsi {} not found".format(name))
- def delete(self, name, force=False):
+ def delete(self, name, force=False, wait=False):
nsi = self.get(name)
querystring = ''
if force:
querystring = '?FORCE=True'
http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
nsi['_id'], querystring))
- #print 'HTTP CODE: {}'.format(http_code)
- #print 'RESP: {}'.format(resp)
+ # print 'HTTP CODE: {}'.format(http_code)
+ # print 'RESP: {}'.format(resp)
if http_code == 202:
- print('Deletion in progress')
+ if wait and resp:
+ resp = json.loads(resp)
+ # Wait for status for NSI instance deletion
+ # For the 'delete' operation, '_id' is used
+ self._wait(resp.get('_id'), deleteFlag=True)
+ else:
+ print('Deletion in progress')
elif http_code == 204:
print('Deleted')
else:
def create(self, nst_name, nsi_name, account, config=None,
ssh_keys=None, description='default description',
- admin_status='ENABLED'):
+ admin_status='ENABLED', wait=False):
nst = self._client.nst.get(nst_name)
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {} '.format(
resp))
+ if wait:
+ # Wait for status for NSI instance creation
+ self._wait(resp.get('nsilcmop_id'))
print(resp['id'])
else:
msg = ""
"""
from osmclient.common import utils
+from osmclient.common import wait as WaitForStatus
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
import json
self._apiBase = '{}{}{}'.format(self._apiName,
self._apiVersion, self._apiResource)
- def create(self, name, sdn_controller):
+ # SDNC '--wait' option
+ def _wait(self, id, deleteFlag=False):
+ # Endpoint to get operation status
+ apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/sdns')
+ # Wait for status for SDN instance creation/update/deletion
+ WaitForStatus.wait_for_status(
+ 'SDNC',
+ str(id),
+ WaitForStatus.TIMEOUT_SDNC_OPERATION,
+ apiUrlStatus,
+ self._http.get2_cmd,
+ deleteFlag=deleteFlag)
+
+ def _get_id_for_wait(self, name):
+ # Returns id of name, or the id itself if given as argument
+ for sdnc in self.list():
+ if name == sdnc['name']:
+ return sdnc['_id']
+ for wim in self.list():
+ if name == sdnc['_id']:
+ return sdnc['_id']
+ return ''
+
+ def create(self, name, sdn_controller, wait=False):
http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
postfields_dict=sdn_controller)
#print 'HTTP CODE: {}'.format(http_code)
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for SDNC instance creation
+ self._wait(resp.get('id'))
print(resp['id'])
else:
msg = ""
msg = resp
raise ClientException("failed to create SDN controller {} - {}".format(name, msg))
- def update(self, name, sdn_controller):
+ def update(self, name, sdn_controller, wait=False):
sdnc = self.get(name)
+ sdnc_id_for_wait = self._get_id_for_wait(name)
http_code, resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,sdnc['_id']),
postfields_dict=sdn_controller)
#print 'HTTP CODE: {}'.format(http_code)
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for SDNC instance update
+ self._wait(sdnc_id_for_wait)
print(resp['id'])
else:
msg = ""
msg = resp
raise ClientException("failed to update SDN controller {} - {}".format(name, msg))
- def delete(self, name, force=False):
+ def delete(self, name, force=False, wait=False):
sdn_controller = self.get(name)
+ sdnc_id_for_wait = self._get_id_for_wait(name)
querystring = ''
if force:
querystring = '?FORCE=True'
#print 'HTTP CODE: {}'.format(http_code)
#print 'RESP: {}'.format(resp)
if http_code == 202:
- print('Deletion in progress')
+ if wait:
+ # Wait for status for SDNC instance deletion
+ self._wait(sdnc_id_for_wait, deleteFlag=True)
+ else:
+ print('Deletion in progress')
elif http_code == 204:
print('Deleted')
elif resp and 'result' in resp:
"""
from osmclient.common import utils
+from osmclient.common import wait as WaitForStatus
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
import yaml
self._apiResource = '/vim_accounts'
self._apiBase = '{}{}{}'.format(self._apiName,
self._apiVersion, self._apiResource)
- def create(self, name, vim_access, sdn_controller=None, sdn_port_mapping=None):
+ # VIM '--wait' option
+ def _wait(self, id, deleteFlag=False):
+ # Endpoint to get operation status
+ apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/vim_accounts')
+ # Wait for status for VIM instance creation/deletion
+ WaitForStatus.wait_for_status(
+ 'VIM',
+ str(id),
+ WaitForStatus.TIMEOUT_VIM_OPERATION,
+ apiUrlStatus,
+ self._http.get2_cmd,
+ deleteFlag=deleteFlag)
+
+ def create(self, name, vim_access, sdn_controller=None, sdn_port_mapping=None, wait=False):
if 'vim-type' not in vim_access:
#'openstack' not in vim_access['vim-type']):
raise Exception("vim type not provided")
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for VIM instance creation
+ self._wait(resp.get('id'))
print(resp['id'])
else:
msg = ""
msg = resp
raise ClientException("failed to create vim {} - {}".format(name, msg))
- def update(self, vim_name, vim_account, sdn_controller, sdn_port_mapping):
+ def update(self, vim_name, vim_account, sdn_controller, sdn_port_mapping, wait=False):
vim = self.get(vim_name)
vim_config = {}
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for VIM instance update
+ self._wait(resp.get('id'))
print(resp['id'])
else:
msg = ""
return vim['uuid']
raise NotFound("vim {} not found".format(name))
- def delete(self, vim_name, force=False):
+ def delete(self, vim_name, force=False, wait=False):
vim_id = vim_name
if not utils.validate_uuid4(vim_name):
vim_id = self.get_id(vim_name)
#print 'HTTP CODE: {}'.format(http_code)
#print 'RESP: {}'.format(resp)
if http_code == 202:
- print('Deletion in progress')
+ if wait:
+ # When deleting an account, 'resp' may be None.
+ # In such a case, the 'id' from 'resp' cannot be used, so use 'vim_id' instead
+ wait_id = vim_id
+ if resp:
+ resp = json.loads(resp)
+ wait_id = resp.get('id')
+ # Wait for status for VIM account deletion
+ self._wait(wait_id, deleteFlag=True)
+ else:
+ print('Deletion in progress')
elif http_code == 204:
print('Deleted')
else:
"""
from osmclient.common import utils
+from osmclient.common import wait as WaitForStatus
from osmclient.common.exceptions import ClientException
from osmclient.common.exceptions import NotFound
import yaml
self._apiResource = '/wim_accounts'
self._apiBase = '{}{}{}'.format(self._apiName,
self._apiVersion, self._apiResource)
- def create(self, name, wim_input, wim_port_mapping=None):
+ # WIM '--wait' option
+ def _wait(self, id, deleteFlag=False):
+ # Endpoint to get operation status
+ apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/wim_accounts')
+ # Wait for status for WIM instance creation/deletion
+ WaitForStatus.wait_for_status(
+ 'WIM',
+ str(id),
+ WaitForStatus.TIMEOUT_WIM_OPERATION,
+ apiUrlStatus,
+ self._http.get2_cmd,
+ deleteFlag=deleteFlag)
+
+ def _get_id_for_wait(self, name):
+ # Returns id of name, or the id itself if given as argument
+ for wim in self.list():
+ if name == wim['name']:
+ return wim['uuid']
+ for wim in self.list():
+ if name == wim['uuid']:
+ return wim['uuid']
+ return ''
+
+ def create(self, name, wim_input, wim_port_mapping=None, wait=False):
if 'wim_type' not in wim_input:
raise Exception("wim type not provided")
if not resp or 'id' not in resp:
raise ClientException('unexpected response from server - {}'.format(
resp))
+ if wait:
+ # Wait for status for WIM instance creation
+ self._wait(resp.get('id'))
print(resp['id'])
else:
msg = ""
msg = resp
raise ClientException("failed to create wim {} - {}".format(name, msg))
- def update(self, wim_name, wim_account, wim_port_mapping=None):
+ def update(self, wim_name, wim_account, wim_port_mapping=None, wait=False):
wim = self.get(wim_name)
-
+ wim_id_for_wait = self._get_id_for_wait(wim_name)
wim_config = {}
if 'config' in wim_account:
if wim_account.get('config')=="" and (wim_port_mapping):
#print 'HTTP CODE: {}'.format(http_code)
#print 'RESP: {}'.format(resp)
if http_code in (200, 201, 202, 204):
- pass
+ if wait:
+ # 'resp' may be None.
+ # In that case, 'resp['id']' cannot be used.
+ # In that case, 'resp['id']' cannot be used, so use the previously obtained id instead
+ wait_id = wim_id_for_wait
+ if resp:
+ resp = json.loads(resp)
+ wait_id = resp.get('id')
+ # Wait for status for WIM instance update
+ self._wait(wait_id)
+ else:
+ pass
else:
msg = ""
if resp:
return wim_account
def get_id(self, name):
- """Returns a VIM id from a VIM name
+ """Returns a WIM id from a WIM name
"""
for wim in self.list():
if name == wim['name']:
return wim['uuid']
raise NotFound("wim {} not found".format(name))
- def delete(self, wim_name, force=False):
+ def delete(self, wim_name, force=False, wait=False):
wim_id = wim_name
+ wim_id_for_wait = self._get_id_for_wait(wim_name)
if not utils.validate_uuid4(wim_name):
wim_id = self.get_id(wim_name)
querystring = ''
querystring = '?FORCE=True'
http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
wim_id, querystring))
- #print 'HTTP CODE: {}'.format(http_code)
- #print 'RESP: {}'.format(resp)
+ # print 'HTTP CODE: {}'.format(http_code)
+ # print 'RESP: {}'.format(resp)
+ # print 'WIM_ID: {}'.format(wim_id)
if http_code == 202:
- print('Deletion in progress')
+ if wait:
+ # 'resp' may be None.
+ # In that case, 'resp['id']' cannot be used, so use the previously obtained id instead
+ wait_id = wim_id_for_wait
+ if resp:
+ resp = json.loads(resp)
+ wait_id = resp.get('id')
+ # Wait for status for WIM account deletion
+ self._wait(wait_id, deleteFlag=True)
+ else:
+ print('Deletion in progress')
elif http_code == 204:
print('Deleted')
else:
else:
return resp
raise NotFound("wim {} not found".format(name))
-