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