Coverage for osmclient/cli_commands/nslcm.py: 29%

194 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2024-06-22 09:01 +0000

1# Copyright ETSI Contributors and Others. 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16import click 

17from osmclient.common.exceptions import ClientException 

18from osmclient.common.utils import validate_uuid4 

19from osmclient.cli_commands import utils 

20import yaml 

21import logging 

22 

23logger = logging.getLogger("osmclient") 

24 

25 

26@click.command( 

27 name="ns-action", short_help="executes an action/primitive over a NS instance" 

28) 

29@click.argument("ns_name") 

30@click.option( 

31 "--vnf_name", 

32 default=None, 

33 help="member-vnf-index if the target is a vnf instead of a ns)", 

34) 

35@click.option("--kdu_name", default=None, help="kdu-name if the target is a kdu)") 

36@click.option("--vdu_id", default=None, help="vdu-id if the target is a vdu") 

37@click.option( 

38 "--vdu_count", default=None, type=int, help="number of vdu instance of this vdu_id" 

39) 

40@click.option("--action_name", prompt=True, help="action name") 

41@click.option("--params", default=None, help="action params in YAML/JSON inline string") 

42@click.option("--params_file", default=None, help="YAML/JSON file with action params") 

43@click.option( 

44 "--timeout", required=False, default=None, type=int, help="timeout in seconds" 

45) 

46@click.option( 

47 "--wait", 

48 required=False, 

49 default=False, 

50 is_flag=True, 

51 help="do not return the control immediately, but keep it until the operation is completed, or timeout", 

52) 

53@click.pass_context 

54def ns_action( 

55 ctx, 

56 ns_name, 

57 vnf_name, 

58 kdu_name, 

59 vdu_id, 

60 vdu_count, 

61 action_name, 

62 params, 

63 params_file, 

64 timeout, 

65 wait, 

66): 

67 """executes an action/primitive over a NS instance 

68 

69 NS_NAME: name or ID of the NS instance 

70 """ 

71 logger.debug("") 

72 utils.check_client_version(ctx.obj, ctx.command.name) 

73 op_data = {} 

74 if vnf_name: 

75 op_data["member_vnf_index"] = vnf_name 

76 if kdu_name: 

77 op_data["kdu_name"] = kdu_name 

78 if vdu_id: 

79 op_data["vdu_id"] = vdu_id 

80 if vdu_count is not None: 

81 op_data["vdu_count_index"] = vdu_count 

82 if timeout: 

83 op_data["timeout_ns_action"] = timeout 

84 op_data["primitive"] = action_name 

85 if params_file: 

86 with open(params_file, "r") as pf: 

87 params = pf.read() 

88 if params: 

89 op_data["primitive_params"] = yaml.safe_load(params) 

90 else: 

91 op_data["primitive_params"] = {} 

92 print(ctx.obj.ns.exec_op(ns_name, op_name="action", op_data=op_data, wait=wait)) 

93 

94 

95@click.command( 

96 name="vnf-scale", short_help="executes a VNF scale (adding/removing VDUs)" 

97) 

98@click.argument("ns_name") 

99@click.argument("vnf_name") 

100@click.option( 

101 "--scaling-group", prompt=True, help="scaling-group-descriptor name to use" 

102) 

103@click.option( 

104 "--scale-in", default=False, is_flag=True, help="performs a scale in operation" 

105) 

106@click.option( 

107 "--scale-out", 

108 default=False, 

109 is_flag=True, 

110 help="performs a scale out operation (by default)", 

111) 

112@click.option( 

113 "--timeout", required=False, default=None, type=int, help="timeout in seconds" 

114) 

115@click.option( 

116 "--wait", 

117 required=False, 

118 default=False, 

119 is_flag=True, 

120 help="do not return the control immediately, but keep it until the operation is completed, or timeout", 

121) 

122@click.pass_context 

123def vnf_scale( 

124 ctx, ns_name, vnf_name, scaling_group, scale_in, scale_out, timeout, wait 

125): 

126 """ 

127 Executes a VNF scale (adding/removing VDUs) 

128 

129 \b 

130 NS_NAME: name or ID of the NS instance. 

131 VNF_NAME: member-vnf-index in the NS to be scaled. 

132 """ 

133 logger.debug("") 

134 utils.check_client_version(ctx.obj, ctx.command.name) 

135 if not scale_in and not scale_out: 

136 scale_out = True 

137 ctx.obj.ns.scale_vnf( 

138 ns_name, vnf_name, scaling_group, scale_in, scale_out, wait, timeout 

139 ) 

140 

141 

142@click.command(name="ns-update", short_help="executes an update of a Network Service.") 

143@click.argument("ns_name") 

144@click.option( 

145 "--updatetype", required=True, type=str, help="available types: CHANGE_VNFPKG" 

146) 

147@click.option( 

148 "--config", 

149 required=True, 

150 type=str, 

151 help="extra information for update operation as YAML/JSON inline string as --config" 

152 " '{changeVnfPackageData:[{vnfInstanceId: xxx, vnfdId: yyy}]}'", 

153) 

154@click.option( 

155 "--timeout", required=False, default=None, type=int, help="timeout in seconds" 

156) 

157@click.option( 

158 "--wait", 

159 required=False, 

160 default=False, 

161 is_flag=True, 

162 help="do not return the control immediately, but keep it until the operation is completed, or timeout", 

163) 

164@click.pass_context 

165def ns_update(ctx, ns_name, updatetype, config, timeout, wait): 

166 """Executes an update of a Network Service. 

167 

168 The update will check new revisions of the Network Functions that are part of the 

169 Network Service, and it will update them if needed. 

170 Sample update command: osm ns-update ns_instance_id --updatetype CHANGE_VNFPKG 

171 --config '{changeVnfPackageData: [{vnfInstanceId: id_x,vnfdId: id_y}]}' --timeout 300 --wait 

172 

173 NS_NAME: Network service instance name or ID. 

174 

175 """ 

176 op_data = { 

177 "timeout": timeout, 

178 "updateType": updatetype, 

179 } 

180 if config: 

181 op_data["config"] = yaml.safe_load(config) 

182 

183 utils.check_client_version(ctx.obj, ctx.command.name) 

184 ctx.obj.ns.update(ns_name, op_data, wait=wait) 

185 

186 

187def iterator_split(iterator, separators): 

188 """ 

189 Splits a tuple or list into several lists whenever a separator is found 

190 For instance, the following tuple will be separated with the separator "--vnf" as follows. 

191 From: 

192 ("--vnf", "A", "--cause", "cause_A", "--vdu", "vdu_A1", "--vnf", "B", "--cause", "cause_B", ... 

193 "--vdu", "vdu_B1", "--count_index", "1", "--run-day1", "--vdu", "vdu_B1", "--count_index", "2") 

194 To: 

195 [ 

196 ("--vnf", "A", "--cause", "cause_A", "--vdu", "vdu_A1"), 

197 ("--vnf", "B", "--cause", "cause_B", "--vdu", "vdu_B1", "--count_index", "1", "--run-day1", ... 

198 "--vdu", "vdu_B1", "--count_index", "2") 

199 ] 

200 

201 Returns as many lists as separators are found 

202 """ 

203 logger.debug("") 

204 if iterator[0] not in separators: 

205 raise ClientException(f"Expected one of {separators}. Received: {iterator[0]}.") 

206 list_of_lists = [] 

207 first = 0 

208 for i in range(len(iterator)): 

209 if iterator[i] in separators: 

210 if i == first: 

211 continue 

212 if i - first < 2: 

213 raise ClientException( 

214 f"Expected at least one argument after separator (possible separators: {separators})." 

215 ) 

216 list_of_lists.append(list(iterator[first:i])) 

217 first = i 

218 if (len(iterator) - first) < 2: 

219 raise ClientException( 

220 f"Expected at least one argument after separator (possible separators: {separators})." 

221 ) 

222 else: 

223 list_of_lists.append(list(iterator[first : len(iterator)])) 

224 # logger.debug(f"List of lists: {list_of_lists}") 

225 return list_of_lists 

226 

227 

228def process_common_heal_params(heal_vnf_dict, args): 

229 logger.debug("") 

230 current_item = "vnf" 

231 i = 0 

232 while i < len(args): 

233 if args[i] == "--cause": 

234 if (i + 1 >= len(args)) or args[i + 1].startswith("--"): 

235 raise ClientException("No cause was provided after --cause") 

236 heal_vnf_dict["cause"] = args[i + 1] 

237 i = i + 2 

238 continue 

239 if args[i] == "--run-day1": 

240 if current_item == "vnf": 

241 if "additionalParams" not in heal_vnf_dict: 

242 heal_vnf_dict["additionalParams"] = {} 

243 heal_vnf_dict["additionalParams"]["run-day1"] = True 

244 else: 

245 # if current_item == "vdu" 

246 heal_vnf_dict["additionalParams"]["vdu"][-1]["run-day1"] = True 

247 i = i + 1 

248 continue 

249 if args[i] == "--vdu": 

250 if "additionalParams" not in heal_vnf_dict: 

251 heal_vnf_dict["additionalParams"] = {} 

252 heal_vnf_dict["additionalParams"]["vdu"] = [] 

253 if (i + 1 >= len(args)) or args[i + 1].startswith("--"): 

254 raise ClientException("No VDU ID was provided after --vdu") 

255 heal_vnf_dict["additionalParams"]["vdu"].append({"vdu-id": args[i + 1]}) 

256 current_item = "vdu" 

257 i = i + 2 

258 continue 

259 if args[i] == "--count-index": 

260 if current_item == "vnf": 

261 raise ClientException( 

262 "Option --count-index only applies to VDU, not to VNF" 

263 ) 

264 if (i + 1 >= len(args)) or args[i + 1].startswith("--"): 

265 raise ClientException("No count index was provided after --count-index") 

266 heal_vnf_dict["additionalParams"]["vdu"][-1]["count-index"] = int( 

267 args[i + 1] 

268 ) 

269 i = i + 2 

270 continue 

271 i = i + 1 

272 return 

273 

274 

275def process_ns_heal_params(ctx, param, value): 

276 """ 

277 Processes the params in the command ns-heal 

278 Click does not allow advanced patterns for positional options like this: 

279 --vnf volumes_vnf --cause "Heal several_volumes-VM of several_volumes_vnf" 

280 --vdu several_volumes-VM 

281 --vnf charm_vnf --cause "Heal two VMs of native_manual_scale_charm_vnf" 

282 --vdu mgmtVM --count-index 1 --run-day1 

283 --vdu mgmtVM --count-index 2 

284 

285 It returns the dictionary with all the params stored in ctx.params["heal_params"] 

286 """ 

287 logger.debug("") 

288 # logger.debug(f"Args: {value}") 

289 if param.name != "args": 

290 raise ClientException(f"Unexpected param: {param.name}") 

291 # Split the tuple "value" by "--vnf" 

292 vnfs = iterator_split(value, ["--vnf"]) 

293 logger.debug(f"VNFs: {vnfs}") 

294 heal_dict = {} 

295 heal_dict["healVnfData"] = [] 

296 for vnf in vnfs: 

297 # logger.debug(f"VNF: {vnf}") 

298 heal_vnf = {} 

299 if vnf[1].startswith("--"): 

300 raise ClientException("Expected a VNF_ID after --vnf") 

301 heal_vnf["vnfInstanceId"] = vnf[1] 

302 process_common_heal_params(heal_vnf, vnf[2:]) 

303 heal_dict["healVnfData"].append(heal_vnf) 

304 ctx.params["heal_params"] = heal_dict 

305 return 

306 

307 

308@click.command( 

309 name="ns-heal", 

310 short_help="heals (recreates) VNFs or VDUs of a NS instance", 

311 context_settings=dict( 

312 ignore_unknown_options=True, 

313 ), 

314) 

315@click.argument("ns_name") 

316@click.argument( 

317 "args", 

318 nargs=-1, 

319 type=click.UNPROCESSED, 

320 callback=process_ns_heal_params, 

321) 

322@click.option("--timeout", type=int, default=None, help="timeout in seconds") 

323@click.option( 

324 "--wait", 

325 default=False, 

326 is_flag=True, 

327 help="do not return the control immediately, but keep it until the operation is completed, or timeout", 

328) 

329@click.pass_context 

330def ns_heal(ctx, ns_name, args, heal_params, timeout, wait): 

331 """heals (recreates) VNFs or VDUs of a NS instance 

332 

333 NS_NAME: name or ID of the NS instance 

334 

335 \b 

336 Options: 

337 --vnf TEXT VNF instance ID or VNF id in the NS [required] 

338 --cause TEXT human readable cause of the healing 

339 --run-day1 indicates whether or not to run day1 primitives for the VNF/VDU 

340 --vdu TEXT vdu-id 

341 --count-index INTEGER count-index 

342 

343 \b 

344 Example: 

345 osm ns-heal NS_NAME|NS_ID --vnf volumes_vnf --cause "Heal several_volumes-VM of several_volumes_vnf" 

346 --vdu several_volumes-VM 

347 --vnf charm_vnf --cause "Heal two VMs of native_manual_scale_charm_vnf" 

348 --vdu mgmtVM --count-index 1 --run-day1 

349 --vdu mgmtVM --count-index 2 

350 """ 

351 logger.debug("") 

352 heal_dict = ctx.params["heal_params"] 

353 logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") 

354 # replace VNF id in the NS by the VNF instance ID 

355 for vnf in heal_dict["healVnfData"]: 

356 vnf_id = vnf["vnfInstanceId"] 

357 if not validate_uuid4(vnf_id): 

358 vnf_filter = f"member-vnf-index-ref={vnf_id}" 

359 vnf_list = ctx.obj.vnf.list(ns=ns_name, filter=vnf_filter) 

360 if len(vnf_list) == 0: 

361 raise ClientException( 

362 f"No VNF found in NS {ns_name} with filter {vnf_filter}" 

363 ) 

364 elif len(vnf_list) == 1: 

365 vnf["vnfInstanceId"] = vnf_list[0]["_id"] 

366 else: 

367 raise ClientException( 

368 f"More than 1 VNF found in NS {ns_name} with filter {vnf_filter}" 

369 ) 

370 logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") 

371 utils.check_client_version(ctx.obj, ctx.command.name) 

372 ctx.obj.ns.heal(ns_name, heal_dict, wait, timeout) 

373 

374 

375def process_vnf_heal_params(ctx, param, value): 

376 """ 

377 Processes the params in the command vnf-heal 

378 Click does not allow advanced patterns for positional options like this: 

379 --vdu mgmtVM --count-index 1 --run-day1 --vdu mgmtVM --count-index 2 

380 

381 It returns the dictionary with all the params stored in ctx.params["heal_params"] 

382 """ 

383 logger.debug("") 

384 # logger.debug(f"Args: {value}") 

385 if param.name != "args": 

386 raise ClientException(f"Unexpected param: {param.name}") 

387 # Split the tuple "value" by "--vnf" 

388 vnf = value 

389 heal_dict = {} 

390 heal_dict["healVnfData"] = [] 

391 logger.debug(f"VNF: {vnf}") 

392 heal_vnf = {"vnfInstanceId": "id_to_be_substituted"} 

393 process_common_heal_params(heal_vnf, vnf) 

394 heal_dict["healVnfData"].append(heal_vnf) 

395 ctx.params["heal_params"] = heal_dict 

396 return 

397 

398 

399@click.command( 

400 name="vnf-heal", 

401 short_help="heals (recreates) a VNF instance or the VDUs of a VNF instance", 

402 context_settings=dict( 

403 ignore_unknown_options=True, 

404 ), 

405) 

406@click.argument("vnf_name") 

407@click.argument( 

408 "args", 

409 nargs=-1, 

410 type=click.UNPROCESSED, 

411 callback=process_vnf_heal_params, 

412) 

413@click.option("--timeout", type=int, default=None, help="timeout in seconds") 

414@click.option( 

415 "--wait", 

416 default=False, 

417 is_flag=True, 

418 help="do not return the control immediately, but keep it until the operation is completed, or timeout", 

419) 

420@click.pass_context 

421def vnf_heal( 

422 ctx, 

423 vnf_name, 

424 args, 

425 heal_params, 

426 timeout, 

427 wait, 

428): 

429 """heals (recreates) a VNF instance or the VDUs of a VNF instance 

430 

431 VNF_NAME: name or ID of the VNF instance 

432 

433 \b 

434 Options: 

435 --cause TEXT human readable cause of the healing of the VNF 

436 --run-day1 indicates whether or not to run day1 primitives for the VNF/VDU 

437 --vdu TEXT vdu-id 

438 --count-index INTEGER count-index 

439 

440 \b 

441 Example: 

442 osm vnf-heal VNF_INSTANCE_ID --vdu mgmtVM --count-index 1 --run-day1 

443 --vdu mgmtVM --count-index 2 

444 """ 

445 logger.debug("") 

446 heal_dict = ctx.params["heal_params"] 

447 heal_dict["healVnfData"][-1]["vnfInstanceId"] = vnf_name 

448 logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") 

449 utils.check_client_version(ctx.obj, ctx.command.name) 

450 vnfr = ctx.obj.vnf.get(vnf_name) 

451 ns_id = vnfr["nsr-id-ref"] 

452 ctx.obj.ns.heal(ns_id, heal_dict, wait, timeout)