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 |
1 |
import click |
17 |
1 |
from osmclient.common.exceptions import ClientException |
18 |
1 |
from osmclient.common.utils import validate_uuid4 |
19 |
1 |
from osmclient.cli_commands import utils |
20 |
1 |
import yaml |
21 |
1 |
import logging |
22 |
|
|
23 |
1 |
logger = logging.getLogger("osmclient") |
24 |
|
|
25 |
|
|
26 |
1 |
@click.command( |
27 |
|
name="ns-action", short_help="executes an action/primitive over a NS instance" |
28 |
|
) |
29 |
1 |
@click.argument("ns_name") |
30 |
1 |
@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 |
1 |
@click.option("--kdu_name", default=None, help="kdu-name if the target is a kdu)") |
36 |
1 |
@click.option("--vdu_id", default=None, help="vdu-id if the target is a vdu") |
37 |
1 |
@click.option( |
38 |
|
"--vdu_count", default=None, type=int, help="number of vdu instance of this vdu_id" |
39 |
|
) |
40 |
1 |
@click.option("--action_name", prompt=True, help="action name") |
41 |
1 |
@click.option("--params", default=None, help="action params in YAML/JSON inline string") |
42 |
1 |
@click.option("--params_file", default=None, help="YAML/JSON file with action params") |
43 |
1 |
@click.option( |
44 |
|
"--timeout", required=False, default=None, type=int, help="timeout in seconds" |
45 |
|
) |
46 |
1 |
@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 |
1 |
@click.pass_context |
54 |
1 |
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 |
0 |
logger.debug("") |
72 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
73 |
0 |
op_data = {} |
74 |
0 |
if vnf_name: |
75 |
0 |
op_data["member_vnf_index"] = vnf_name |
76 |
0 |
if kdu_name: |
77 |
0 |
op_data["kdu_name"] = kdu_name |
78 |
0 |
if vdu_id: |
79 |
0 |
op_data["vdu_id"] = vdu_id |
80 |
0 |
if vdu_count is not None: |
81 |
0 |
op_data["vdu_count_index"] = vdu_count |
82 |
0 |
if timeout: |
83 |
0 |
op_data["timeout_ns_action"] = timeout |
84 |
0 |
op_data["primitive"] = action_name |
85 |
0 |
if params_file: |
86 |
0 |
with open(params_file, "r") as pf: |
87 |
0 |
params = pf.read() |
88 |
0 |
if params: |
89 |
0 |
op_data["primitive_params"] = yaml.safe_load(params) |
90 |
|
else: |
91 |
0 |
op_data["primitive_params"] = {} |
92 |
0 |
print(ctx.obj.ns.exec_op(ns_name, op_name="action", op_data=op_data, wait=wait)) |
93 |
|
|
94 |
|
|
95 |
1 |
@click.command( |
96 |
|
name="vnf-scale", short_help="executes a VNF scale (adding/removing VDUs)" |
97 |
|
) |
98 |
1 |
@click.argument("ns_name") |
99 |
1 |
@click.argument("vnf_name") |
100 |
1 |
@click.option( |
101 |
|
"--scaling-group", prompt=True, help="scaling-group-descriptor name to use" |
102 |
|
) |
103 |
1 |
@click.option( |
104 |
|
"--scale-in", default=False, is_flag=True, help="performs a scale in operation" |
105 |
|
) |
106 |
1 |
@click.option( |
107 |
|
"--scale-out", |
108 |
|
default=False, |
109 |
|
is_flag=True, |
110 |
|
help="performs a scale out operation (by default)", |
111 |
|
) |
112 |
1 |
@click.option( |
113 |
|
"--timeout", required=False, default=None, type=int, help="timeout in seconds" |
114 |
|
) |
115 |
1 |
@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 |
1 |
@click.pass_context |
123 |
1 |
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 |
0 |
logger.debug("") |
134 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
135 |
0 |
if not scale_in and not scale_out: |
136 |
0 |
scale_out = True |
137 |
0 |
ctx.obj.ns.scale_vnf( |
138 |
|
ns_name, vnf_name, scaling_group, scale_in, scale_out, wait, timeout |
139 |
|
) |
140 |
|
|
141 |
|
|
142 |
1 |
@click.command(name="ns-update", short_help="executes an update of a Network Service.") |
143 |
1 |
@click.argument("ns_name") |
144 |
1 |
@click.option( |
145 |
|
"--updatetype", required=True, type=str, help="available types: CHANGE_VNFPKG" |
146 |
|
) |
147 |
1 |
@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 |
1 |
@click.option( |
155 |
|
"--timeout", required=False, default=None, type=int, help="timeout in seconds" |
156 |
|
) |
157 |
1 |
@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 |
1 |
@click.pass_context |
165 |
1 |
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 |
0 |
op_data = { |
177 |
|
"timeout": timeout, |
178 |
|
"updateType": updatetype, |
179 |
|
} |
180 |
0 |
if config: |
181 |
0 |
op_data["config"] = yaml.safe_load(config) |
182 |
|
|
183 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
184 |
0 |
ctx.obj.ns.update(ns_name, op_data, wait=wait) |
185 |
|
|
186 |
|
|
187 |
1 |
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 |
0 |
logger.debug("") |
204 |
0 |
if iterator[0] not in separators: |
205 |
0 |
raise ClientException(f"Expected one of {separators}. Received: {iterator[0]}.") |
206 |
0 |
list_of_lists = [] |
207 |
0 |
first = 0 |
208 |
0 |
for i in range(len(iterator)): |
209 |
0 |
if iterator[i] in separators: |
210 |
0 |
if i == first: |
211 |
0 |
continue |
212 |
0 |
if i - first < 2: |
213 |
0 |
raise ClientException( |
214 |
|
f"Expected at least one argument after separator (possible separators: {separators})." |
215 |
|
) |
216 |
0 |
list_of_lists.append(list(iterator[first:i])) |
217 |
0 |
first = i |
218 |
0 |
if (len(iterator) - first) < 2: |
219 |
0 |
raise ClientException( |
220 |
|
f"Expected at least one argument after separator (possible separators: {separators})." |
221 |
|
) |
222 |
|
else: |
223 |
0 |
list_of_lists.append(list(iterator[first : len(iterator)])) |
224 |
|
# logger.debug(f"List of lists: {list_of_lists}") |
225 |
0 |
return list_of_lists |
226 |
|
|
227 |
|
|
228 |
1 |
def process_common_heal_params(heal_vnf_dict, args): |
229 |
0 |
logger.debug("") |
230 |
0 |
current_item = "vnf" |
231 |
0 |
i = 0 |
232 |
0 |
while i < len(args): |
233 |
0 |
if args[i] == "--cause": |
234 |
0 |
if (i + 1 >= len(args)) or args[i + 1].startswith("--"): |
235 |
0 |
raise ClientException("No cause was provided after --cause") |
236 |
0 |
heal_vnf_dict["cause"] = args[i + 1] |
237 |
0 |
i = i + 2 |
238 |
0 |
continue |
239 |
0 |
if args[i] == "--run-day1": |
240 |
0 |
if current_item == "vnf": |
241 |
0 |
if "additionalParams" not in heal_vnf_dict: |
242 |
0 |
heal_vnf_dict["additionalParams"] = {} |
243 |
0 |
heal_vnf_dict["additionalParams"]["run-day1"] = True |
244 |
|
else: |
245 |
|
# if current_item == "vdu" |
246 |
0 |
heal_vnf_dict["additionalParams"]["vdu"][-1]["run-day1"] = True |
247 |
0 |
i = i + 1 |
248 |
0 |
continue |
249 |
0 |
if args[i] == "--vdu": |
250 |
0 |
if "additionalParams" not in heal_vnf_dict: |
251 |
0 |
heal_vnf_dict["additionalParams"] = {} |
252 |
0 |
heal_vnf_dict["additionalParams"]["vdu"] = [] |
253 |
0 |
if (i + 1 >= len(args)) or args[i + 1].startswith("--"): |
254 |
0 |
raise ClientException("No VDU ID was provided after --vdu") |
255 |
0 |
heal_vnf_dict["additionalParams"]["vdu"].append({"vdu-id": args[i + 1]}) |
256 |
0 |
current_item = "vdu" |
257 |
0 |
i = i + 2 |
258 |
0 |
continue |
259 |
0 |
if args[i] == "--count-index": |
260 |
0 |
if current_item == "vnf": |
261 |
0 |
raise ClientException( |
262 |
|
"Option --count-index only applies to VDU, not to VNF" |
263 |
|
) |
264 |
0 |
if (i + 1 >= len(args)) or args[i + 1].startswith("--"): |
265 |
0 |
raise ClientException("No count index was provided after --count-index") |
266 |
0 |
heal_vnf_dict["additionalParams"]["vdu"][-1]["count-index"] = int( |
267 |
|
args[i + 1] |
268 |
|
) |
269 |
0 |
i = i + 2 |
270 |
0 |
continue |
271 |
0 |
i = i + 1 |
272 |
0 |
return |
273 |
|
|
274 |
|
|
275 |
1 |
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 |
0 |
logger.debug("") |
288 |
|
# logger.debug(f"Args: {value}") |
289 |
0 |
if param.name != "args": |
290 |
0 |
raise ClientException(f"Unexpected param: {param.name}") |
291 |
|
# Split the tuple "value" by "--vnf" |
292 |
0 |
vnfs = iterator_split(value, ["--vnf"]) |
293 |
0 |
logger.debug(f"VNFs: {vnfs}") |
294 |
0 |
heal_dict = {} |
295 |
0 |
heal_dict["healVnfData"] = [] |
296 |
0 |
for vnf in vnfs: |
297 |
|
# logger.debug(f"VNF: {vnf}") |
298 |
0 |
heal_vnf = {} |
299 |
0 |
if vnf[1].startswith("--"): |
300 |
0 |
raise ClientException("Expected a VNF_ID after --vnf") |
301 |
0 |
heal_vnf["vnfInstanceId"] = vnf[1] |
302 |
0 |
process_common_heal_params(heal_vnf, vnf[2:]) |
303 |
0 |
heal_dict["healVnfData"].append(heal_vnf) |
304 |
0 |
ctx.params["heal_params"] = heal_dict |
305 |
0 |
return |
306 |
|
|
307 |
|
|
308 |
1 |
@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 |
1 |
@click.argument("ns_name") |
316 |
1 |
@click.argument( |
317 |
|
"args", |
318 |
|
nargs=-1, |
319 |
|
type=click.UNPROCESSED, |
320 |
|
callback=process_ns_heal_params, |
321 |
|
) |
322 |
1 |
@click.option("--timeout", type=int, default=None, help="timeout in seconds") |
323 |
1 |
@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 |
1 |
@click.pass_context |
330 |
1 |
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 |
0 |
logger.debug("") |
352 |
0 |
heal_dict = ctx.params["heal_params"] |
353 |
0 |
logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") |
354 |
|
# replace VNF id in the NS by the VNF instance ID |
355 |
0 |
for vnf in heal_dict["healVnfData"]: |
356 |
0 |
vnf_id = vnf["vnfInstanceId"] |
357 |
0 |
if not validate_uuid4(vnf_id): |
358 |
0 |
vnf_filter = f"member-vnf-index-ref={vnf_id}" |
359 |
0 |
vnf_list = ctx.obj.vnf.list(ns=ns_name, filter=vnf_filter) |
360 |
0 |
if len(vnf_list) == 0: |
361 |
0 |
raise ClientException( |
362 |
|
f"No VNF found in NS {ns_name} with filter {vnf_filter}" |
363 |
|
) |
364 |
0 |
elif len(vnf_list) == 1: |
365 |
0 |
vnf["vnfInstanceId"] = vnf_list[0]["_id"] |
366 |
|
else: |
367 |
0 |
raise ClientException( |
368 |
|
f"More than 1 VNF found in NS {ns_name} with filter {vnf_filter}" |
369 |
|
) |
370 |
0 |
logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") |
371 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
372 |
0 |
ctx.obj.ns.heal(ns_name, heal_dict, wait, timeout) |
373 |
|
|
374 |
|
|
375 |
1 |
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 |
0 |
logger.debug("") |
384 |
|
# logger.debug(f"Args: {value}") |
385 |
0 |
if param.name != "args": |
386 |
0 |
raise ClientException(f"Unexpected param: {param.name}") |
387 |
|
# Split the tuple "value" by "--vnf" |
388 |
0 |
vnf = value |
389 |
0 |
heal_dict = {} |
390 |
0 |
heal_dict["healVnfData"] = [] |
391 |
0 |
logger.debug(f"VNF: {vnf}") |
392 |
0 |
heal_vnf = {"vnfInstanceId": "id_to_be_substituted"} |
393 |
0 |
process_common_heal_params(heal_vnf, vnf) |
394 |
0 |
heal_dict["healVnfData"].append(heal_vnf) |
395 |
0 |
ctx.params["heal_params"] = heal_dict |
396 |
0 |
return |
397 |
|
|
398 |
|
|
399 |
1 |
@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 |
1 |
@click.argument("vnf_name") |
407 |
1 |
@click.argument( |
408 |
|
"args", |
409 |
|
nargs=-1, |
410 |
|
type=click.UNPROCESSED, |
411 |
|
callback=process_vnf_heal_params, |
412 |
|
) |
413 |
1 |
@click.option("--timeout", type=int, default=None, help="timeout in seconds") |
414 |
1 |
@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 |
1 |
@click.pass_context |
421 |
1 |
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 |
0 |
logger.debug("") |
446 |
0 |
heal_dict = ctx.params["heal_params"] |
447 |
0 |
heal_dict["healVnfData"][-1]["vnfInstanceId"] = vnf_name |
448 |
0 |
logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") |
449 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
450 |
0 |
vnfr = ctx.obj.vnf.get(vnf_name) |
451 |
0 |
ns_id = vnfr["nsr-id-ref"] |
452 |
0 |
ctx.obj.ns.heal(ns_id, heal_dict, wait, timeout) |