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 import print_output |
19 |
1 |
from osmclient.cli_commands import utils |
20 |
1 |
from prettytable import PrettyTable |
21 |
1 |
import yaml |
22 |
1 |
import json |
23 |
1 |
from datetime import datetime |
24 |
1 |
import logging |
25 |
|
|
26 |
1 |
logger = logging.getLogger("osmclient") |
27 |
|
|
28 |
|
|
29 |
1 |
@click.command(name="ns-list", short_help="list all NS instances") |
30 |
1 |
@click.option( |
31 |
|
"--filter", |
32 |
|
default=None, |
33 |
|
multiple=True, |
34 |
|
help="restricts the list to the NS instances matching the filter.", |
35 |
|
) |
36 |
1 |
@click.option( |
37 |
|
"--long", |
38 |
|
is_flag=True, |
39 |
|
help="get more details of the NS (project, vim, deployment status, configuration status.", |
40 |
|
) |
41 |
1 |
@print_output.output_option |
42 |
1 |
@click.pass_context |
43 |
1 |
def ns_list(ctx, filter, long, output): |
44 |
|
"""list all NS instances |
45 |
|
|
46 |
|
\b |
47 |
|
Options: |
48 |
|
--filter filterExpr Restricts the list to the NS instances matching the filter |
49 |
|
|
50 |
|
\b |
51 |
|
filterExpr consists of one or more strings formatted according to "simpleFilterExpr", |
52 |
|
concatenated using the "&" character: |
53 |
|
|
54 |
|
\b |
55 |
|
filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]* |
56 |
|
simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]* |
57 |
|
op := "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" |
58 |
|
attrName := string |
59 |
|
value := scalar value |
60 |
|
|
61 |
|
\b |
62 |
|
where: |
63 |
|
* zero or more occurrences |
64 |
|
? zero or one occurrence |
65 |
|
[] grouping of expressions to be used with ? and * |
66 |
|
"" quotation marks for marking string constants |
67 |
|
<> name separator |
68 |
|
|
69 |
|
\b |
70 |
|
"AttrName" is the name of one attribute in the data type that defines the representation |
71 |
|
of the resource. The dot (".") character in "simpleFilterExpr" allows concatenation of |
72 |
|
<attrName> entries to filter by attributes deeper in the hierarchy of a structured document. |
73 |
|
"Op" stands for the comparison operator. If the expression has concatenated <attrName> |
74 |
|
entries, it means that the operator "op" is applied to the attribute addressed by the last |
75 |
|
<attrName> entry included in the concatenation. All simple filter expressions are combined |
76 |
|
by the "AND" logical operator. In a concatenation of <attrName> entries in a <simpleFilterExpr>, |
77 |
|
the rightmost "attrName" entry in a "simpleFilterExpr" is called "leaf attribute". The |
78 |
|
concatenation of all "attrName" entries except the leaf attribute is called the "attribute |
79 |
|
prefix". If an attribute referenced in an expression is an array, an object that contains a |
80 |
|
corresponding array shall be considered to match the expression if any of the elements in the |
81 |
|
array matches all expressions that have the same attribute prefix. |
82 |
|
|
83 |
|
\b |
84 |
|
Filter examples: |
85 |
|
--filter admin-status=ENABLED |
86 |
|
--filter nsd-ref=<NSD_NAME> |
87 |
|
--filter nsd.vendor=<VENDOR> |
88 |
|
--filter nsd.vendor=<VENDOR>&nsd-ref=<NSD_NAME> |
89 |
|
--filter nsd.constituent-vnfd.vnfd-id-ref=<VNFD_NAME> |
90 |
|
""" |
91 |
|
|
92 |
0 |
def summarize_deployment_status(status_dict): |
93 |
|
# Nets |
94 |
0 |
summary = "" |
95 |
0 |
if not status_dict: |
96 |
0 |
return summary |
97 |
0 |
n_nets = 0 |
98 |
0 |
status_nets = {} |
99 |
0 |
net_list = status_dict.get("nets", []) |
100 |
0 |
for net in net_list: |
101 |
0 |
n_nets += 1 |
102 |
0 |
if net["status"] not in status_nets: |
103 |
0 |
status_nets[net["status"]] = 1 |
104 |
|
else: |
105 |
0 |
status_nets[net["status"]] += 1 |
106 |
0 |
message = "Nets: " |
107 |
0 |
for k, v in status_nets.items(): |
108 |
0 |
message += "{}:{},".format(k, v) |
109 |
0 |
message += "TOTAL:{}".format(n_nets) |
110 |
0 |
summary += "{}".format(message) |
111 |
|
# VMs and VNFs |
112 |
0 |
n_vms = 0 |
113 |
0 |
status_vms = {} |
114 |
0 |
status_vnfs = {} |
115 |
0 |
vnf_list = status_dict["vnfs"] |
116 |
0 |
for vnf in vnf_list: |
117 |
0 |
member_vnf_index = vnf["member_vnf_index"] |
118 |
0 |
if member_vnf_index not in status_vnfs: |
119 |
0 |
status_vnfs[member_vnf_index] = {} |
120 |
0 |
for vm in vnf["vms"]: |
121 |
0 |
n_vms += 1 |
122 |
0 |
if vm["status"] not in status_vms: |
123 |
0 |
status_vms[vm["status"]] = 1 |
124 |
|
else: |
125 |
0 |
status_vms[vm["status"]] += 1 |
126 |
0 |
if vm["status"] not in status_vnfs[member_vnf_index]: |
127 |
0 |
status_vnfs[member_vnf_index][vm["status"]] = 1 |
128 |
|
else: |
129 |
0 |
status_vnfs[member_vnf_index][vm["status"]] += 1 |
130 |
0 |
message = "VMs: " |
131 |
0 |
for k, v in status_vms.items(): |
132 |
0 |
message += "{}:{},".format(k, v) |
133 |
0 |
message += "TOTAL:{}".format(n_vms) |
134 |
0 |
summary += "\n{}".format(message) |
135 |
0 |
summary += "\nNFs:" |
136 |
0 |
for k, v in status_vnfs.items(): |
137 |
0 |
total = 0 |
138 |
0 |
message = "\n {} VMs: ".format(k) |
139 |
0 |
for k2, v2 in v.items(): |
140 |
0 |
message += "{}:{},".format(k2, v2) |
141 |
0 |
total += v2 |
142 |
0 |
message += "TOTAL:{}".format(total) |
143 |
0 |
summary += message |
144 |
0 |
return summary |
145 |
|
|
146 |
0 |
def summarize_config_status(ee_list): |
147 |
0 |
summary = "" |
148 |
0 |
if not ee_list: |
149 |
0 |
return summary |
150 |
0 |
n_ee = 0 |
151 |
0 |
status_ee = {} |
152 |
0 |
for ee in ee_list: |
153 |
0 |
n_ee += 1 |
154 |
0 |
if ee["elementType"] not in status_ee: |
155 |
0 |
status_ee[ee["elementType"]] = {} |
156 |
0 |
status_ee[ee["elementType"]][ee["status"]] = 1 |
157 |
0 |
continue |
158 |
0 |
if ee["status"] in status_ee[ee["elementType"]]: |
159 |
0 |
status_ee[ee["elementType"]][ee["status"]] += 1 |
160 |
|
else: |
161 |
0 |
status_ee[ee["elementType"]][ee["status"]] = 1 |
162 |
0 |
for elementType in ["KDU", "VDU", "PDU", "VNF", "NS"]: |
163 |
0 |
if elementType in status_ee: |
164 |
0 |
message = "" |
165 |
0 |
total = 0 |
166 |
0 |
for k, v in status_ee[elementType].items(): |
167 |
0 |
message += "{}:{},".format(k, v) |
168 |
0 |
total += v |
169 |
0 |
message += "TOTAL:{}\n".format(total) |
170 |
0 |
summary += "{}: {}".format(elementType, message) |
171 |
0 |
summary += "TOTAL Exec. Env.: {}".format(n_ee) |
172 |
0 |
return summary |
173 |
|
|
174 |
0 |
logger.debug("") |
175 |
0 |
if filter: |
176 |
0 |
utils.check_client_version(ctx.obj, "--filter") |
177 |
0 |
filter = "&".join(filter) |
178 |
0 |
resp = ctx.obj.ns.list(filter) |
179 |
|
else: |
180 |
0 |
resp = ctx.obj.ns.list() |
181 |
0 |
if long: |
182 |
0 |
table = PrettyTable( |
183 |
|
[ |
184 |
|
"ns instance name", |
185 |
|
"id", |
186 |
|
"date", |
187 |
|
"ns state", |
188 |
|
"current operation", |
189 |
|
"error details", |
190 |
|
"project", |
191 |
|
"vim (inst param)", |
192 |
|
"deployment status", |
193 |
|
"configuration status", |
194 |
|
] |
195 |
|
) |
196 |
0 |
project_list = ctx.obj.project.list() |
197 |
0 |
try: |
198 |
0 |
vim_list = ctx.obj.vim.list() |
199 |
0 |
except Exception: |
200 |
0 |
vim_list = [] |
201 |
|
else: |
202 |
0 |
table = PrettyTable( |
203 |
|
[ |
204 |
|
"ns instance name", |
205 |
|
"id", |
206 |
|
"date", |
207 |
|
"ns state", |
208 |
|
"current operation", |
209 |
|
"error details", |
210 |
|
] |
211 |
|
) |
212 |
0 |
for ns in resp: |
213 |
0 |
nsr = ns |
214 |
0 |
logger.debug("NS info: {}".format(nsr)) |
215 |
0 |
nsr_name = nsr["name"] |
216 |
0 |
nsr_id = nsr["_id"] |
217 |
0 |
date = datetime.fromtimestamp(nsr["create-time"]).strftime("%Y-%m-%dT%H:%M:%S") |
218 |
0 |
ns_state = nsr.get("nsState", nsr["_admin"]["nsState"]) |
219 |
0 |
if long: |
220 |
0 |
deployment_status = summarize_deployment_status(nsr.get("deploymentStatus")) |
221 |
0 |
config_status = summarize_config_status(nsr.get("configurationStatus")) |
222 |
0 |
project_id, project_name = utils.get_project(project_list, nsr) |
223 |
|
# project = '{} ({})'.format(project_name, project_id) |
224 |
0 |
project = project_name |
225 |
0 |
vim_id = nsr.get("datacenter") |
226 |
0 |
vim_name = utils.get_vim_name(vim_list, vim_id) |
227 |
|
|
228 |
|
# vim = '{} ({})'.format(vim_name, vim_id) |
229 |
0 |
vim = vim_name |
230 |
0 |
if "currentOperation" in nsr: |
231 |
0 |
current_operation = "{} ({})".format( |
232 |
|
nsr["currentOperation"], nsr["currentOperationID"] |
233 |
|
) |
234 |
|
else: |
235 |
0 |
current_operation = "{} ({})".format( |
236 |
|
nsr["_admin"].get("current-operation", "-"), |
237 |
|
nsr["_admin"]["nslcmop"], |
238 |
|
) |
239 |
0 |
error_details = "N/A" |
240 |
0 |
if ( |
241 |
|
ns_state == "BROKEN" |
242 |
|
or ns_state == "DEGRADED" |
243 |
|
or ("currentOperation" not in nsr and nsr.get("errorDescription")) |
244 |
|
): |
245 |
0 |
error_details = "{}\nDetail: {}".format( |
246 |
|
nsr["errorDescription"], nsr["errorDetail"] |
247 |
|
) |
248 |
|
|
249 |
0 |
if long: |
250 |
0 |
table.add_row( |
251 |
|
[ |
252 |
|
nsr_name, |
253 |
|
nsr_id, |
254 |
|
date, |
255 |
|
ns_state, |
256 |
|
current_operation, |
257 |
|
utils.wrap_text(text=error_details, width=40), |
258 |
|
project, |
259 |
|
vim, |
260 |
|
deployment_status, |
261 |
|
config_status, |
262 |
|
] |
263 |
|
) |
264 |
|
else: |
265 |
0 |
table.add_row( |
266 |
|
[ |
267 |
|
nsr_name, |
268 |
|
nsr_id, |
269 |
|
date, |
270 |
|
ns_state, |
271 |
|
current_operation, |
272 |
|
utils.wrap_text(text=error_details, width=40), |
273 |
|
] |
274 |
|
) |
275 |
0 |
print_output.print_output(output, table.field_names, table._rows) |
276 |
0 |
print('To get the history of all operations over a NS, run "osm ns-op-list NS_ID"') |
277 |
0 |
print( |
278 |
|
'For more details on the current operation, run "osm ns-op-show OPERATION_ID"' |
279 |
|
) |
280 |
|
|
281 |
|
|
282 |
1 |
@click.command(name="ns-show", short_help="shows the info of a NS instance") |
283 |
1 |
@click.argument("name") |
284 |
1 |
@click.option("--literal", is_flag=True, help="print literally, no pretty table") |
285 |
1 |
@click.option( |
286 |
|
"--filter", |
287 |
|
multiple=True, |
288 |
|
help="restricts the information to the fields in the filter", |
289 |
|
) |
290 |
1 |
@print_output.output_option |
291 |
1 |
@click.pass_context |
292 |
1 |
def ns_show(ctx, name, literal, filter, output): |
293 |
|
"""shows the info of a NS instance |
294 |
|
|
295 |
|
NAME: name or ID of the NS instance |
296 |
|
""" |
297 |
0 |
logger.debug("") |
298 |
0 |
ns = ctx.obj.ns.get(name) |
299 |
|
|
300 |
0 |
if literal: |
301 |
0 |
print(yaml.safe_dump(ns, indent=4, default_flow_style=False)) |
302 |
0 |
return |
303 |
|
|
304 |
0 |
table = PrettyTable(["field", "value"]) |
305 |
|
|
306 |
0 |
for k, v in list(ns.items()): |
307 |
0 |
if not filter or k in filter: |
308 |
0 |
table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) |
309 |
|
|
310 |
0 |
print_output.print_output(output, table.field_names, table._rows) |
311 |
|
|
312 |
|
|
313 |
1 |
@click.command(name="ns-create", short_help="creates a new Network Service instance") |
314 |
1 |
@click.option("--ns_name", prompt=True, help="name of the NS instance") |
315 |
1 |
@click.option("--nsd_name", prompt=True, help="name of the NS descriptor") |
316 |
1 |
@click.option( |
317 |
|
"--vim_account", |
318 |
|
prompt=True, |
319 |
|
help="default VIM account id or name for the deployment", |
320 |
|
) |
321 |
1 |
@click.option("--admin_status", default="ENABLED", help="administration status") |
322 |
1 |
@click.option( |
323 |
|
"--ssh_keys", |
324 |
|
default=None, |
325 |
|
help="comma separated list of public key files to inject to vnfs", |
326 |
|
) |
327 |
1 |
@click.option("--config", default=None, help="ns specific yaml configuration") |
328 |
1 |
@click.option("--config_file", default=None, help="ns specific yaml configuration file") |
329 |
1 |
@click.option( |
330 |
|
"--wait", |
331 |
|
required=False, |
332 |
|
default=False, |
333 |
|
is_flag=True, |
334 |
|
help="do not return the control immediately, but keep it " |
335 |
|
"until the operation is completed, or timeout", |
336 |
|
) |
337 |
1 |
@click.option("--timeout", default=None, help="ns deployment timeout") |
338 |
1 |
@click.pass_context |
339 |
1 |
def ns_create( |
340 |
|
ctx, |
341 |
|
nsd_name, |
342 |
|
ns_name, |
343 |
|
vim_account, |
344 |
|
admin_status, |
345 |
|
ssh_keys, |
346 |
|
config, |
347 |
|
config_file, |
348 |
|
wait, |
349 |
|
timeout, |
350 |
|
): |
351 |
|
"""creates a new NS instance""" |
352 |
0 |
logger.debug("") |
353 |
0 |
if config_file: |
354 |
0 |
utils.check_client_version(ctx.obj, "--config_file") |
355 |
0 |
if config: |
356 |
0 |
raise ClientException( |
357 |
|
'"--config" option is incompatible with "--config_file" option' |
358 |
|
) |
359 |
0 |
with open(config_file, "r") as cf: |
360 |
0 |
config = cf.read() |
361 |
0 |
ctx.obj.ns.create( |
362 |
|
nsd_name, |
363 |
|
ns_name, |
364 |
|
config=config, |
365 |
|
ssh_keys=ssh_keys, |
366 |
|
account=vim_account, |
367 |
|
wait=wait, |
368 |
|
timeout=timeout, |
369 |
|
) |
370 |
|
|
371 |
|
|
372 |
1 |
@click.command(name="ns-delete", short_help="deletes a NS instance") |
373 |
1 |
@click.argument("name") |
374 |
1 |
@click.option( |
375 |
|
"--force", is_flag=True, help="forces the deletion bypassing pre-conditions" |
376 |
|
) |
377 |
1 |
@click.option( |
378 |
|
"--config", |
379 |
|
default=None, |
380 |
|
help="specific yaml configuration for the termination, e.g. '{autoremove: False, timeout_ns_terminate: " |
381 |
|
"600, skip_terminate_primitives: True}'", |
382 |
|
) |
383 |
1 |
@click.option( |
384 |
|
"--wait", |
385 |
|
required=False, |
386 |
|
default=False, |
387 |
|
is_flag=True, |
388 |
|
help="do not return the control immediately, but keep it " |
389 |
|
"until the operation is completed, or timeout", |
390 |
|
) |
391 |
1 |
@click.pass_context |
392 |
1 |
def ns_delete(ctx, name, force, config, wait): |
393 |
|
"""deletes a NS instance |
394 |
|
|
395 |
|
NAME: name or ID of the NS instance to be deleted |
396 |
|
""" |
397 |
0 |
logger.debug("") |
398 |
0 |
if not force: |
399 |
0 |
ctx.obj.ns.delete(name, config=config, wait=wait) |
400 |
|
else: |
401 |
0 |
utils.check_client_version(ctx.obj, "--force") |
402 |
0 |
ctx.obj.ns.delete(name, force, config=config, wait=wait) |