Refactor -o option to simplify code
[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 @print_output.output_option
42 @click.pass_context
43 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 def summarize_deployment_status(status_dict):
93 # Nets
94 summary = ""
95 if not status_dict:
96 return summary
97 n_nets = 0
98 status_nets = {}
99 net_list = status_dict.get("nets", [])
100 for net in net_list:
101 n_nets += 1
102 if net["status"] not in status_nets:
103 status_nets[net["status"]] = 1
104 else:
105 status_nets[net["status"]] += 1
106 message = "Nets: "
107 for k, v in status_nets.items():
108 message += "{}:{},".format(k, v)
109 message += "TOTAL:{}".format(n_nets)
110 summary += "{}".format(message)
111 # VMs and VNFs
112 n_vms = 0
113 status_vms = {}
114 status_vnfs = {}
115 vnf_list = status_dict["vnfs"]
116 for vnf in vnf_list:
117 member_vnf_index = vnf["member_vnf_index"]
118 if member_vnf_index not in status_vnfs:
119 status_vnfs[member_vnf_index] = {}
120 for vm in vnf["vms"]:
121 n_vms += 1
122 if vm["status"] not in status_vms:
123 status_vms[vm["status"]] = 1
124 else:
125 status_vms[vm["status"]] += 1
126 if vm["status"] not in status_vnfs[member_vnf_index]:
127 status_vnfs[member_vnf_index][vm["status"]] = 1
128 else:
129 status_vnfs[member_vnf_index][vm["status"]] += 1
130 message = "VMs: "
131 for k, v in status_vms.items():
132 message += "{}:{},".format(k, v)
133 message += "TOTAL:{}".format(n_vms)
134 summary += "\n{}".format(message)
135 summary += "\nNFs:"
136 for k, v in status_vnfs.items():
137 total = 0
138 message = "\n {} VMs: ".format(k)
139 for k2, v2 in v.items():
140 message += "{}:{},".format(k2, v2)
141 total += v2
142 message += "TOTAL:{}".format(total)
143 summary += message
144 return summary
145
146 def summarize_config_status(ee_list):
147 summary = ""
148 if not ee_list:
149 return summary
150 n_ee = 0
151 status_ee = {}
152 for ee in ee_list:
153 n_ee += 1
154 if ee["elementType"] not in status_ee:
155 status_ee[ee["elementType"]] = {}
156 status_ee[ee["elementType"]][ee["status"]] = 1
157 continue
158 if ee["status"] in status_ee[ee["elementType"]]:
159 status_ee[ee["elementType"]][ee["status"]] += 1
160 else:
161 status_ee[ee["elementType"]][ee["status"]] = 1
162 for elementType in ["KDU", "VDU", "PDU", "VNF", "NS"]:
163 if elementType in status_ee:
164 message = ""
165 total = 0
166 for k, v in status_ee[elementType].items():
167 message += "{}:{},".format(k, v)
168 total += v
169 message += "TOTAL:{}\n".format(total)
170 summary += "{}: {}".format(elementType, message)
171 summary += "TOTAL Exec. Env.: {}".format(n_ee)
172 return summary
173
174 logger.debug("")
175 if filter:
176 utils.check_client_version(ctx.obj, "--filter")
177 filter = "&".join(filter)
178 resp = ctx.obj.ns.list(filter)
179 else:
180 resp = ctx.obj.ns.list()
181 if long:
182 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 project_list = ctx.obj.project.list()
197 try:
198 vim_list = ctx.obj.vim.list()
199 except Exception:
200 vim_list = []
201 else:
202 table = PrettyTable(
203 [
204 "ns instance name",
205 "id",
206 "date",
207 "ns state",
208 "current operation",
209 "error details",
210 ]
211 )
212 for ns in resp:
213 fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
214 if fullclassname == "osmclient.sol005.client.Client":
215 nsr = ns
216 logger.debug("NS info: {}".format(nsr))
217 nsr_name = nsr["name"]
218 nsr_id = nsr["_id"]
219 date = datetime.fromtimestamp(nsr["create-time"]).strftime(
220 "%Y-%m-%dT%H:%M:%S"
221 )
222 ns_state = nsr.get("nsState", nsr["_admin"]["nsState"])
223 if long:
224 deployment_status = summarize_deployment_status(
225 nsr.get("deploymentStatus")
226 )
227 config_status = summarize_config_status(nsr.get("configurationStatus"))
228 project_id, project_name = utils.get_project(project_list, nsr)
229 # project = '{} ({})'.format(project_name, project_id)
230 project = project_name
231 vim_id = nsr.get("datacenter")
232 vim_name = utils.get_vim_name(vim_list, vim_id)
233
234 # vim = '{} ({})'.format(vim_name, vim_id)
235 vim = vim_name
236 if "currentOperation" in nsr:
237 current_operation = "{} ({})".format(
238 nsr["currentOperation"], nsr["currentOperationID"]
239 )
240 else:
241 current_operation = "{} ({})".format(
242 nsr["_admin"].get("current-operation", "-"),
243 nsr["_admin"]["nslcmop"],
244 )
245 error_details = "N/A"
246 if (
247 ns_state == "BROKEN"
248 or ns_state == "DEGRADED"
249 or ("currentOperation" not in nsr and nsr.get("errorDescription"))
250 ):
251 error_details = "{}\nDetail: {}".format(
252 nsr["errorDescription"], nsr["errorDetail"]
253 )
254 else:
255 nsopdata = ctx.obj.ns.get_opdata(ns["id"])
256 nsr = nsopdata["nsr:nsr"]
257 nsr_name = nsr["name-ref"]
258 nsr_id = nsr["ns-instance-config-ref"]
259 date = "-"
260 project = "-"
261 deployment_status = (
262 nsr["operational-status"]
263 if "operational-status" in nsr
264 else "Not found"
265 )
266 ns_state = deployment_status
267 config_status = nsr.get("config-status", "Not found")
268 current_operation = "Unknown"
269 error_details = nsr.get("detailed-status", "Not found")
270 if config_status == "config_not_needed":
271 config_status = "configured (no charms)"
272
273 if long:
274 table.add_row(
275 [
276 nsr_name,
277 nsr_id,
278 date,
279 ns_state,
280 current_operation,
281 utils.wrap_text(text=error_details, width=40),
282 project,
283 vim,
284 deployment_status,
285 config_status,
286 ]
287 )
288 else:
289 table.add_row(
290 [
291 nsr_name,
292 nsr_id,
293 date,
294 ns_state,
295 current_operation,
296 utils.wrap_text(text=error_details, width=40),
297 ]
298 )
299 print_output.print_output(output, table.field_names, table._rows)
300 print('To get the history of all operations over a NS, run "osm ns-op-list NS_ID"')
301 print(
302 'For more details on the current operation, run "osm ns-op-show OPERATION_ID"'
303 )
304
305
306 @click.command(name="ns-show", short_help="shows the info of a NS instance")
307 @click.argument("name")
308 @click.option("--literal", is_flag=True, help="print literally, no pretty table")
309 @click.option(
310 "--filter",
311 multiple=True,
312 help="restricts the information to the fields in the filter",
313 )
314 @print_output.output_option
315 @click.pass_context
316 def ns_show(ctx, name, literal, filter, output):
317 """shows the info of a NS instance
318
319 NAME: name or ID of the NS instance
320 """
321 logger.debug("")
322 ns = ctx.obj.ns.get(name)
323
324 if literal:
325 print(yaml.safe_dump(ns, indent=4, default_flow_style=False))
326 return
327
328 table = PrettyTable(["field", "value"])
329
330 for k, v in list(ns.items()):
331 if not filter or k in filter:
332 table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)])
333
334 fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__
335 if fullclassname != "osmclient.sol005.client.Client":
336 nsopdata = ctx.obj.ns.get_opdata(ns["id"])
337 nsr_optdata = nsopdata["nsr:nsr"]
338 for k, v in list(nsr_optdata.items()):
339 if not filter or k in filter:
340 table.add_row([k, utils.wrap_text(json.dumps(v, indent=2), width=100)])
341 print_output.print_output(output, table.field_names, table._rows)
342
343
344 @click.command(name="ns-create", short_help="creates a new Network Service instance")
345 @click.option("--ns_name", prompt=True, help="name of the NS instance")
346 @click.option("--nsd_name", prompt=True, help="name of the NS descriptor")
347 @click.option(
348 "--vim_account",
349 prompt=True,
350 help="default VIM account id or name for the deployment",
351 )
352 @click.option("--admin_status", default="ENABLED", help="administration status")
353 @click.option(
354 "--ssh_keys",
355 default=None,
356 help="comma separated list of public key files to inject to vnfs",
357 )
358 @click.option("--config", default=None, help="ns specific yaml configuration")
359 @click.option("--config_file", default=None, help="ns specific yaml configuration file")
360 @click.option(
361 "--wait",
362 required=False,
363 default=False,
364 is_flag=True,
365 help="do not return the control immediately, but keep it "
366 "until the operation is completed, or timeout",
367 )
368 @click.option("--timeout", default=None, help="ns deployment timeout")
369 @click.pass_context
370 def ns_create(
371 ctx,
372 nsd_name,
373 ns_name,
374 vim_account,
375 admin_status,
376 ssh_keys,
377 config,
378 config_file,
379 wait,
380 timeout,
381 ):
382 """creates a new NS instance"""
383 logger.debug("")
384 if config_file:
385 utils.check_client_version(ctx.obj, "--config_file")
386 if config:
387 raise ClientException(
388 '"--config" option is incompatible with "--config_file" option'
389 )
390 with open(config_file, "r") as cf:
391 config = cf.read()
392 ctx.obj.ns.create(
393 nsd_name,
394 ns_name,
395 config=config,
396 ssh_keys=ssh_keys,
397 account=vim_account,
398 wait=wait,
399 timeout=timeout,
400 )
401
402
403 @click.command(name="ns-delete", short_help="deletes a NS instance")
404 @click.argument("name")
405 @click.option(
406 "--force", is_flag=True, help="forces the deletion bypassing pre-conditions"
407 )
408 @click.option(
409 "--config",
410 default=None,
411 help="specific yaml configuration for the termination, e.g. '{autoremove: False, timeout_ns_terminate: "
412 "600, skip_terminate_primitives: True}'",
413 )
414 @click.option(
415 "--wait",
416 required=False,
417 default=False,
418 is_flag=True,
419 help="do not return the control immediately, but keep it "
420 "until the operation is completed, or timeout",
421 )
422 @click.pass_context
423 def ns_delete(ctx, name, force, config, wait):
424 """deletes a NS instance
425
426 NAME: name or ID of the NS instance to be deleted
427 """
428 logger.debug("")
429 if not force:
430 ctx.obj.ns.delete(name, config=config, wait=wait)
431 else:
432 utils.check_client_version(ctx.obj, "--force")
433 ctx.obj.ns.delete(name, force, config=config, wait=wait)