Enable print_output common function to print json and yaml from python dict or list
[osm/osmclient.git] / osmclient / cli_commands / nslcm.py
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
16 import click
17 from osmclient.common.exceptions import ClientException
18 from osmclient.common.utils import validate_uuid4
19 from osmclient.cli_commands import utils
20 import yaml
21 import logging
22
23 logger = 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
54 def 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
123 def 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
165 def 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
187 def 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
228 def 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
275 def 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
330 def 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
375 def 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
421 def 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)