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