Skip to content

Commit efca8c2

Browse files
author
Simca
committed
Briliat features added
Up command: - Possibility to add nodes to existing cluster Syntax: . up [existing cluster name] [amount of nodes to add] Info, list, up commands: - dynamic columns width which depends on actual content Quota: - detailed usage statistics per user conf: - updated cache mechanics And various small fixes and improvements
1 parent 44fb682 commit efca8c2

File tree

27 files changed

+1338
-563
lines changed

27 files changed

+1338
-563
lines changed

src/openstack_cli/commands/conf/reset_cache.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@
2020

2121

2222
def __init__(conf: Configuration):
23-
conf.invalidate_cache()
23+
for cache_item in conf.list_cache_ext:
24+
print(f"Invalidate cache extention '{cache_item}'...")
25+
conf.get_cache_ext(cache_item).invalidate_all()
2426
print("Cached data flushed")

src/openstack_cli/commands/images.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,27 @@
2121

2222

2323
__module__ = CommandMetaInfo("images", item_help="Display available VM Images")
24-
__args__ = __module__.arg_builder\
24+
__args__ = __module__.arg_builder \
25+
.add_default_argument("search_pattern", str, "Search query", default="")\
2526
.add_argument("all", bool, "Display all available images", default=False)\
2627
.add_argument("snapshots", bool, "Display only snapshots", default=False)\
27-
.add_default_argument("search_pattern", str, "Search query", default="")
28+
.add_argument("own", bool, "Display only own items", default=False)
2829

2930

30-
def __init__(conf: Configuration, search_pattern: str, snapshots: bool, all: bool):
31+
32+
def __init__(conf: Configuration, search_pattern: str, snapshots: bool, all: bool, own: bool):
3133
ostack = OpenStack(conf)
3234
if snapshots:
33-
show_snap(ostack, search_pattern)
35+
show_snap(conf, ostack, search_pattern, own)
3436
elif all:
35-
show_all(ostack, search_pattern)
37+
show_all(conf, ostack, search_pattern, own)
3638
else:
37-
show_normal(ostack, search_pattern)
39+
show_normal(conf, ostack, search_pattern, own)
3840

3941

40-
def show_snap(ostack: OpenStack, search_pattern: str):
42+
def show_snap(conf: Configuration, ostack: OpenStack, search_pattern: str, own: bool):
4143
images = ostack.images
44+
user_id = conf.user_id
4245

4346
table = TableOutput(
4447
TableColumn("Id", 36),
@@ -51,6 +54,9 @@ def show_snap(ostack: OpenStack, search_pattern: str):
5154
table.print_header()
5255

5356
for image in images:
57+
if own and image.user_id != user_id:
58+
continue
59+
5460
if not image.image_type:
5561
continue
5662

@@ -66,7 +72,7 @@ def show_snap(ostack: OpenStack, search_pattern: str):
6672
)
6773

6874

69-
def show_all(ostack: OpenStack, search_pattern: str):
75+
def show_all(conf: Configuration, ostack: OpenStack, search_pattern: str, own: bool):
7076
images = ostack.images
7177

7278
table = TableOutput(
@@ -95,7 +101,7 @@ def show_all(ostack: OpenStack, search_pattern: str):
95101
)
96102

97103

98-
def show_normal(ostack: OpenStack, search_pattern: str):
104+
def show_normal(conf: Configuration, ostack: OpenStack, search_pattern: str, own: bool):
99105
images = ostack.os_images
100106

101107
table = TableOutput(

src/openstack_cli/commands/info.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,32 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
from enum import Enum
17+
from typing import Dict, List
1618

1719
from openstack_cli.modules.apputils.terminal import TableOutput, TableColumn
1820

21+
from openstack_cli.modules.openstack.objects import ServerPowerState, OpenStackVMInfo
1922
from openstack_cli.modules.apputils.terminal.colors import Colors, Symbols
2023
from openstack_cli.core.config import Configuration
2124
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
2225
from openstack_cli.modules.openstack import OpenStack
26+
from openstack_cli.modules.utils import ValueHolder
2327

2428

2529
__module__ = CommandMetaInfo("info", "Shows the detailed information about requested VMs")
2630
__args__ = __module__.arg_builder\
2731
.add_default_argument("search_pattern", str, "Search query", default="") \
2832
.add_argument("own", bool, "Display only owned by user items", default=False)
2933

30-
from openstack_cli.modules.openstack.objects import ServerPowerState
3134

35+
class WidthConst(Enum):
36+
max_fqdn_len = 0
37+
max_key_len = 1
38+
max_net_len = 2
3239

33-
def __init__(conf: Configuration, search_pattern: str, debug: bool, own: bool):
40+
41+
def print_cluster(servers: Dict[str, List[OpenStackVMInfo]], vh: ValueHolder = None, ostack: OpenStack = None):
3442
__run_ico = Symbols.PLAY.green()
3543
__pause_ico = Symbols.PAUSE.yellow()
3644
__stop_ico = Symbols.STOP.red()
@@ -39,20 +47,19 @@ def __init__(conf: Configuration, search_pattern: str, debug: bool, own: bool):
3947
ServerPowerState.paused: __pause_ico
4048
}
4149

42-
ostack = OpenStack(conf, debug=debug)
43-
clusters = ostack.get_server_by_cluster(search_pattern=search_pattern, sort=True, only_owned=own)
44-
max_fqdn_len = ostack.servers.max_host_len + ostack.servers.max_domain_len + 5
50+
if vh is None:
51+
vh = ValueHolder(3, [50, 30, 15])
4552

4653
to = TableOutput(
4754
TableColumn("", 1, inv_ch=Colors.GREEN.wrap_len()),
48-
TableColumn("Host name", max_fqdn_len),
55+
TableColumn("Host name", vh.get(WidthConst.max_fqdn_len)),
4956
TableColumn("Host IP", 16),
50-
TableColumn("SSH Key", 30),
51-
TableColumn("Network name", length=15)
57+
TableColumn("SSH Key", vh.get(WidthConst.max_key_len)),
58+
TableColumn("Network name", length=vh.get(WidthConst.max_net_len))
5259
)
5360

5461
to.print_header()
55-
for cluster_name, servers in clusters.items():
62+
for cluster_name, servers in servers.items():
5663
servers = sorted(servers, key=lambda x: x.fqdn)
5764
for server in servers:
5865
to.print_row(
@@ -62,3 +69,19 @@ def __init__(conf: Configuration, search_pattern: str, debug: bool, own: bool):
6269
server.key_name,
6370
server.net_name
6471
)
72+
73+
def __init__(conf: Configuration, search_pattern: str, debug: bool, own: bool):
74+
vh: ValueHolder = ValueHolder(3)
75+
def __fake_filter(s: OpenStackVMInfo):
76+
vh.set_if_bigger(WidthConst.max_fqdn_len, len(s.fqdn))
77+
vh.set_if_bigger(WidthConst.max_key_len, len(s.key_name))
78+
vh.set_if_bigger(WidthConst.max_net_len, len(s.net_name))
79+
return False
80+
81+
ostack = OpenStack(conf, debug=debug)
82+
clusters = ostack.get_server_by_cluster(search_pattern=search_pattern, sort=True, only_owned=own,
83+
filter_func=__fake_filter)
84+
85+
print_cluster(clusters, vh, ostack)
86+
87+

src/openstack_cli/commands/list.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
# limitations under the License.
1515

1616
from datetime import datetime
17+
from enum import Enum
1718
from typing import List, Dict
1819

1920
from openstack_cli.modules.apputils.terminal import TableOutput, TableColumn
20-
2121
from openstack_cli.modules.apputils.terminal.colors import Colors, Symbols
22-
from openstack_cli.core.config import Configuration
2322
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
2423

24+
from openstack_cli.core.config import Configuration
25+
from openstack_cli.modules.utils import ValueHolder
2526
from openstack_cli.modules.openstack import OpenStack
2627
from openstack_cli.modules.openstack.objects import ServerPowerState, OpenStackVMInfo
2728

@@ -30,6 +31,10 @@
3031
.add_default_argument("search_pattern", str, "Search query", default="")\
3132
.add_argument("own", bool, "Display only owned by user items", default=False)
3233

34+
class WidthConst(Enum):
35+
max_cluster_name = 0
36+
max_vm_type_len = 1
37+
3338

3439
def get_lifetime(timestamp: datetime):
3540
now = datetime.utcnow()
@@ -46,16 +51,19 @@ def get_lifetime(timestamp: datetime):
4651
return f"{int(hours)}h {int(minutes)}m" if days == 0 else f"{days} day(s)"
4752

4853

49-
def print_cluster(servers: Dict[str, List[OpenStackVMInfo]]):
54+
def print_cluster(servers: Dict[str, List[OpenStackVMInfo]], vh: ValueHolder = None):
5055
__run_ico = Symbols.PLAY.color(Colors.GREEN)
5156
__pause_ico = Symbols.PAUSE.color(Colors.BRIGHT_YELLOW)
5257
__stop_ico = Symbols.STOP.color(Colors.RED)
5358

59+
if vh is None:
60+
vh = ValueHolder(2, [40, 20])
61+
5462
to = TableOutput(
55-
TableColumn("Cluster Name", 40),
63+
TableColumn("Cluster Name", vh.get(WidthConst.max_cluster_name)),
5664
TableColumn("", 5),
5765
TableColumn("Nodes state", 20, inv_ch=Colors.GREEN.wrap_len() * 3),
58-
TableColumn("VmType", 20),
66+
TableColumn("VmType", vh.get(WidthConst.max_vm_type_len)),
5967
TableColumn("Lifetime", 10)
6068
)
6169

@@ -78,11 +86,19 @@ def print_cluster(servers: Dict[str, List[OpenStackVMInfo]]):
7886

7987
def __init__(conf: Configuration, search_pattern: str, own: bool):
8088
ostack = OpenStack(conf)
81-
clusters = ostack.get_server_by_cluster(search_pattern=search_pattern, sort=True, only_owned=own)
89+
90+
vh = ValueHolder(2)
91+
def __fake_filter(s: OpenStackVMInfo):
92+
vh.set_if_bigger(WidthConst.max_cluster_name, len(s.cluster_name))
93+
vh.set_if_bigger(WidthConst.max_vm_type_len, len(s.flavor.name))
94+
return False
95+
96+
clusters = ostack.get_server_by_cluster(search_pattern=search_pattern, sort=True, only_owned=own,
97+
filter_func=__fake_filter)
8298

8399
if search_pattern and len(clusters) == 0:
84100
print(f"Query '{search_pattern}' returned no match")
85101
return
86102

87-
print_cluster(clusters)
103+
print_cluster(clusters, vh)
88104

src/openstack_cli/commands/quota.py

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
from typing import Dict
17+
1618
from openstack_cli.modules.apputils.terminal import TableOutput, TableColumn, TableColumnPosition
1719

1820
from openstack_cli.modules.apputils.terminal.colors import Colors
1921
from openstack_cli.core.config import Configuration
2022
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
21-
from openstack_cli.modules.openstack import OpenStack
22-
23+
from openstack_cli.modules.openstack import OpenStack, OpenStackQuotas, OpenStackQuotaType, OpenStackVMInfo
2324

2425
__module__ = CommandMetaInfo("quota", item_help="Show the allowed resource limits for the project")
26+
__args__ = __module__.arg_builder\
27+
.add_argument("details", bool, "Show detailed resource consumption", default=False)\
28+
.add_argument("show_clusters", bool, "Show user instances on details page", alias="show-clusters", default=False)
2529

2630

2731
def get_percents(current: float, fmax: float):
@@ -40,8 +44,88 @@ def get_progressbar(percents, color=""):
4044
else:
4145
return f"[{'#' * f}{' '* nf}]"
4246

43-
44-
def __init__(conf: Configuration):
47+
def _show_details(conf: Configuration, ostack: OpenStack, quotas: OpenStackQuotas, show_clusters: bool):
48+
project_cpu_max, project_cpu_current = quotas.get_quota(OpenStackQuotaType.CPU_CORES)
49+
project_mem_max, project_mem_current = quotas.get_quota(OpenStackQuotaType.RAM_MB)
50+
project_instances_max, project_instances_current = quotas.get_quota(OpenStackQuotaType.INSTANCES)
51+
52+
servers = ostack.servers
53+
54+
resources = {}
55+
56+
for server in ostack.servers.items:
57+
if server.owner_id not in resources:
58+
resources[server.owner_id] = {
59+
"clusters": {},
60+
OpenStackQuotaType.CPU_CORES: 0,
61+
OpenStackQuotaType.RAM_MB: 0,
62+
OpenStackQuotaType.INSTANCES: 0
63+
}
64+
65+
record = resources[server.owner_id]
66+
67+
record[OpenStackQuotaType.CPU_CORES] += server.flavor.vcpus
68+
record[OpenStackQuotaType.INSTANCES] += 1
69+
record[OpenStackQuotaType.RAM_MB] += server.flavor.raw.ram
70+
if server.cluster_name not in record["clusters"]:
71+
record["clusters"][server.cluster_name] = 0
72+
73+
record["clusters"][server.cluster_name] += 1
74+
75+
resources[server.owner_id] = record
76+
77+
padding = " " * 5
78+
print("Per-User statistic: ")
79+
80+
user_sum_instances: int = 0
81+
user_sum_cpu: int = 0
82+
user_sum_mem: int = 0
83+
for user_id, record in resources.items():
84+
user_name = ostack.users.get_user(user_id)
85+
print(f"User id: {user_name if user_name else 'unknown'} ({user_id})")
86+
user_instances = record[OpenStackQuotaType.INSTANCES]
87+
user_mem = record[OpenStackQuotaType.RAM_MB]
88+
user_cpu = record[OpenStackQuotaType.CPU_CORES]
89+
90+
print(f"{padding}Instances: {user_instances:<8} ({get_percents(user_instances, project_instances_max):0.2f}% from prj.cap.)")
91+
print(f"{padding}CPU : {user_cpu:<8} ({get_percents(user_cpu, project_cpu_max):0.2f}% from prj.cap.)")
92+
print(f"{padding}MEM (GB) : {user_mem/1024:<8.2f} ({get_percents(user_mem, project_mem_max):0.2f}% from prj.cap.)")
93+
if show_clusters:
94+
_clusters = record["clusters"] # {'cluster name': nodes amount}
95+
_clusters = [f"{k} (hosts: {v})" for k, v in _clusters.items()]
96+
97+
title = "Clusters :"
98+
_padding = " " * len(title)
99+
_title_displayed: bool = False
100+
for cluster in _clusters:
101+
if _title_displayed:
102+
print(f"{padding}{_padding} {cluster}")
103+
continue
104+
105+
print(f"{padding}{title} {cluster}")
106+
_title_displayed = True
107+
print()
108+
109+
user_sum_cpu += user_cpu
110+
user_sum_mem += user_mem
111+
user_sum_instances += user_instances
112+
113+
print("--------------------")
114+
print("Summary:")
115+
print(f"{padding}∑ Inst. : {user_sum_instances:<9} "
116+
f"({get_percents(user_sum_instances, project_instances_max):0.2f}% calc./"
117+
f"{get_percents(project_instances_current, project_instances_max):0.2f}% act.)")
118+
119+
print(f"{padding}∑ CPU : {user_sum_cpu:<9} "
120+
f"({get_percents(user_sum_cpu, project_cpu_max):0.2f}% calc./"
121+
f"{get_percents(project_cpu_current, project_cpu_max):0.2f}% act.)")
122+
print(f"{padding}∑ MEM(GB): {user_sum_mem/1024:<9.2f} "
123+
f"({get_percents(user_sum_mem, project_mem_max):0.2f}% calc./"
124+
f"{get_percents(project_mem_current, project_mem_max):0.2f}% act.)")
125+
print()
126+
127+
128+
def __init__(conf: Configuration, details: bool, show_clusters: bool):
45129
stack = OpenStack(conf)
46130
limits = stack.quotas
47131

@@ -72,3 +156,7 @@ def __init__(conf: Configuration):
72156

73157
print()
74158
print("* Used/Avl. - raw metric")
159+
160+
if details:
161+
print("Calculating per-user statistic....")
162+
_show_details(conf, stack, limits, show_clusters)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain 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,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from openstack_cli.core.config import Configuration
17+
from openstack_cli.modules.apputils.discovery import CommandMetaInfo, NotImplementedCommandException
18+
19+
__module__ = CommandMetaInfo("snap", item_help="Manage OpenStack Snapshoots", default_sub_command="list")
20+
21+
22+
def __init__(conf: Configuration):
23+
raise NotImplementedCommandException()

0 commit comments

Comments
 (0)