hcs-cli 0.1.318__py3-none-any.whl → 0.1.320__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hcs_cli/__init__.py +2 -2
- hcs_cli/cmds/advisor/html_utils.py +30 -26
- hcs_cli/cmds/advisor/recommendation_engine.py +7 -10
- hcs_cli/cmds/daas/tenant/plan.py +1 -1
- hcs_cli/cmds/debug/start.py +0 -1
- hcs_cli/cmds/dev/fs/clear.py +8 -0
- hcs_cli/cmds/dev/fs/helper/credential_helper.py +2 -0
- hcs_cli/cmds/dev/fs/helper/k8s_util.py +0 -1
- hcs_cli/cmds/dev/fs/init.py +38 -5
- hcs_cli/cmds/dev/fs/profiler.py +0 -1
- hcs_cli/cmds/dev/fs/provided_files/akka.plan.yml +94 -250
- hcs_cli/cmds/dev/fs/provided_files/azsim.plan.yml +27 -34
- hcs_cli/cmds/dev/fs/provided_files/azure.plan.yml +294 -322
- hcs_cli/cmds/dev/fs/provided_files/mqtt-secret.yaml +188 -93
- hcs_cli/cmds/dev/fs/provided_files/mqtt-server-external.yaml +4 -5
- hcs_cli/cmds/dev/fs/provided_files/patch-mqtt-hostname.yml +3 -3
- hcs_cli/cmds/dev/fs/provided_files/patch-vernemq-ssl-depth.json +1 -1
- hcs_cli/cmds/dev/fs/tailor.py +7 -12
- hcs_cli/cmds/dev/mqtt.py +1 -2
- hcs_cli/cmds/dev/util/mqtt_helper.py +0 -1
- hcs_cli/cmds/hoc/search.py +39 -9
- hcs_cli/cmds/hoc/stats.py +46 -0
- hcs_cli/cmds/hst/clean.py +2 -1
- hcs_cli/cmds/inventory/assign.py +1 -3
- hcs_cli/cmds/inventory/deassign.py +1 -1
- hcs_cli/cmds/inventory/delete.py +48 -0
- hcs_cli/cmds/lcm/provider/create.py +11 -2
- hcs_cli/cmds/lcm/template/expand.py +46 -0
- hcs_cli/cmds/lcm/vm/delete.py +3 -2
- hcs_cli/cmds/scm/plan.py +131 -3
- hcs_cli/cmds/task.py +2 -4
- hcs_cli/cmds/template/expand.py +64 -19
- hcs_cli/cmds/template/list_usage.py +2 -2
- hcs_cli/cmds/template/update.py +2 -2
- hcs_cli/cmds/template/usage.py +20 -7
- hcs_cli/cmds/vm/delete.py +3 -2
- hcs_cli/cmds/vm/list.py +51 -40
- hcs_cli/cmds/vmm/rootca_migrate.py +1 -1
- hcs_cli/config/hcs-deployments.yaml +52 -52
- hcs_cli/main.py +0 -2
- hcs_cli/payload/akka.blueprint.yml +95 -243
- hcs_cli/payload/app/manual.json +19 -19
- hcs_cli/payload/edge/akka.json +6 -6
- hcs_cli/payload/edge/vsphere.json +6 -6
- hcs_cli/payload/hoc/lcm-capcalc.json.template +43 -0
- hcs_cli/payload/hoc/no-spare.json.template +1 -1
- hcs_cli/payload/inventory/assign.json +14 -16
- hcs_cli/payload/inventory/deassign.json +11 -11
- hcs_cli/payload/lcm/akka.json +31 -33
- hcs_cli/payload/lcm/azure-dummy.json +64 -66
- hcs_cli/payload/lcm/azure-real.json +13 -11
- hcs_cli/payload/lcm/edge-proxy.json +34 -36
- hcs_cli/payload/lcm/zero-dedicated.json +34 -36
- hcs_cli/payload/lcm/zero-delay-1m-per-vm.json +53 -69
- hcs_cli/payload/lcm/zero-fail-delete-template.json +43 -0
- hcs_cli/payload/lcm/zero-fail-destroy-onthread.json +38 -40
- hcs_cli/payload/lcm/zero-fail-destroy.json +38 -40
- hcs_cli/payload/lcm/zero-fail-prepare-onthread.json +38 -40
- hcs_cli/payload/lcm/zero-fail-prepare.json +38 -40
- hcs_cli/payload/lcm/zero-fail-vm-onthread.json +58 -74
- hcs_cli/payload/lcm/zero-fail-vm.json +58 -74
- hcs_cli/payload/lcm/zero-floating.json +34 -36
- hcs_cli/payload/lcm/zero-manual.json +33 -35
- hcs_cli/payload/lcm/zero-multisession.json +34 -36
- hcs_cli/payload/lcm/zero-nanw.json +31 -33
- hcs_cli/payload/lcm/zero-new-5k-delay.json +69 -78
- hcs_cli/payload/lcm/zero-new-5k.json +36 -38
- hcs_cli/payload/lcm/zero-new-snapshot.json +37 -39
- hcs_cli/payload/lcm/zero-new.json +37 -39
- hcs_cli/payload/lcm/zero-reuse-vm-id.json +33 -35
- hcs_cli/payload/lcm/zero-with-max-id-offset.json +32 -34
- hcs_cli/payload/lcm/zero.json +59 -73
- hcs_cli/payload/provider/ad-stes-vsphere.json +26 -26
- hcs_cli/payload/provider/akka.json +12 -12
- hcs_cli/payload/provider/azure.json +14 -14
- hcs_cli/payload/provider/edgeproxy.json +12 -12
- hcs_cli/payload/provider/vsphere.json +14 -14
- hcs_cli/payload/scm/starter.json +22 -23
- hcs_cli/payload/synt/core/p01-dummy-success.json +11 -15
- hcs_cli/payload/synt/core/p02-dummy-fail.json +12 -15
- hcs_cli/payload/synt/core/p03-dummy-exception.json +12 -15
- hcs_cli/payload/synt/core/p04-dummy-success-repeat.json +12 -15
- hcs_cli/payload/synt/core/p05-dummy-fail-repeat.json +13 -16
- hcs_cli/payload/synt/core/p06-dummy-exception-repeat.json +13 -16
- hcs_cli/payload/synt/core/p07-dummy-delay.json +12 -15
- hcs_cli/payload/synt/core/p08-dummy-property.json +12 -15
- hcs_cli/payload/synt/ext/p20-connect-success.json +12 -15
- hcs_cli/payload/synt/ext/p21-connect-fail.json +12 -15
- hcs_cli/payload/synt/ext/p30-ssl-success.json +12 -15
- hcs_cli/payload/synt/ext/p31-ssl-fail.json +13 -16
- hcs_cli/payload/synt/ext/p40-http-success.json +12 -15
- hcs_cli/payload/synt/ext/p41-http-fail.json +12 -15
- hcs_cli/payload/synt/ext/p42-http-status-code.json +14 -20
- hcs_cli/payload/synt/ext1/p10-ping-success.json +13 -16
- hcs_cli/payload/synt/ext1/p11-ping-fail.json +12 -15
- hcs_cli/payload/synt/ext1/p12-ping-success-repeat.json +14 -17
- hcs_cli/provider/hcs/cert.py +0 -1
- hcs_cli/provider/hcs/edge.py +1 -1
- hcs_cli/provider/hcs/uag.py +1 -1
- hcs_cli/service/admin/template.py +10 -1
- hcs_cli/service/hoc/diagnostic.py +11 -3
- hcs_cli/service/inventory/__init__.py +15 -2
- hcs_cli/service/inventory/vm.py +12 -0
- hcs_cli/service/lcm/template.py +9 -6
- hcs_cli/service/lcm/vm.py +0 -1
- hcs_cli/service/task.py +0 -1
- hcs_cli/service/template.py +1 -1
- hcs_cli/support/debug_util.py +0 -1
- hcs_cli/support/plan_util.py +0 -1
- hcs_cli/support/predefined_payload.py +4 -1
- hcs_cli/support/template_util.py +0 -1
- hcs_cli/support/test_utils.py +2 -2
- hcs_cli/support/test_utils2.py +536 -0
- hcs_cli/support/vm_table.py +2 -2
- {hcs_cli-0.1.318.dist-info → hcs_cli-0.1.320.dist-info}/METADATA +24 -17
- {hcs_cli-0.1.318.dist-info → hcs_cli-0.1.320.dist-info}/RECORD +118 -113
- hcs_cli/payload/lcm/azure-dummy-nt.json +0 -69
- {hcs_cli-0.1.318.dist-info → hcs_cli-0.1.320.dist-info}/WHEEL +0 -0
- {hcs_cli-0.1.318.dist-info → hcs_cli-0.1.320.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2023-2023 VMware Inc.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
from hcs_core.ctxp import recent
|
|
18
|
+
import hcs_core.sglib.cli_options as cli
|
|
19
|
+
|
|
20
|
+
from hcs_cli.service import inventory
|
|
21
|
+
from hcs_cli.support.param_util import parse_vm_path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command()
|
|
25
|
+
@cli.org_id
|
|
26
|
+
@click.argument("vm_path", type=str, required=False)
|
|
27
|
+
def delete(org: str, vm_path: str):
|
|
28
|
+
"""Delete a VM by path, e.g., template1/vm1, or 'vm1,vm2,vm3'."""
|
|
29
|
+
|
|
30
|
+
org_id = cli.get_org_id(org)
|
|
31
|
+
|
|
32
|
+
if vm_path.find(",") > 0:
|
|
33
|
+
vm_ids = [v.strip() for v in vm_path.split(",")]
|
|
34
|
+
template_id = recent.require("template", None)
|
|
35
|
+
else:
|
|
36
|
+
template_id, vm_id = parse_vm_path(vm_path)
|
|
37
|
+
ret = inventory.get(template_id, vm_id, org_id)
|
|
38
|
+
if not ret:
|
|
39
|
+
return "", 1
|
|
40
|
+
vm_ids = [vm_id]
|
|
41
|
+
|
|
42
|
+
ret = {"_requested_ids": vm_ids, "_accepted_ids": [], "_deleted": 0}
|
|
43
|
+
vms = inventory.begin_deleting_vms_by_id(template_id, org_id, vm_ids)
|
|
44
|
+
for vm in vms:
|
|
45
|
+
ret["_accepted_ids"].append(vm["id"])
|
|
46
|
+
actual_deleted = inventory.finish_deleting_vms(template_id, org_id, vm_ids)
|
|
47
|
+
ret["_deleted"] = actual_deleted
|
|
48
|
+
return ret
|
|
@@ -63,6 +63,12 @@ from hcs_cli.support import constant
|
|
|
63
63
|
required=False,
|
|
64
64
|
help="",
|
|
65
65
|
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--subscription-id",
|
|
68
|
+
type=str,
|
|
69
|
+
required=False,
|
|
70
|
+
help="",
|
|
71
|
+
)
|
|
66
72
|
@click.option(
|
|
67
73
|
"--file",
|
|
68
74
|
"-f",
|
|
@@ -80,6 +86,7 @@ def create(
|
|
|
80
86
|
client_id: str,
|
|
81
87
|
client_secret: str,
|
|
82
88
|
tenant_id: str,
|
|
89
|
+
subscription_id: str,
|
|
83
90
|
file: str,
|
|
84
91
|
org: str,
|
|
85
92
|
**kwargs,
|
|
@@ -108,7 +115,7 @@ def create(
|
|
|
108
115
|
"description": description,
|
|
109
116
|
"credentialId": credential_id,
|
|
110
117
|
}
|
|
111
|
-
elif client_id or client_secret or tenant_id:
|
|
118
|
+
elif client_id or client_secret or tenant_id or subscription_id:
|
|
112
119
|
if type != "AZURE":
|
|
113
120
|
raise click.BadParameter("client-id is only supported for Azure provider.")
|
|
114
121
|
if not client_id:
|
|
@@ -117,7 +124,8 @@ def create(
|
|
|
117
124
|
raise click.BadParameter("client-secret is required for client-id.")
|
|
118
125
|
if not tenant_id:
|
|
119
126
|
raise click.BadParameter("tenant-id is required for Azure provider.")
|
|
120
|
-
|
|
127
|
+
if not subscription_id:
|
|
128
|
+
raise click.BadParameter("subscription-id is required for Azure provider.")
|
|
121
129
|
data = {
|
|
122
130
|
"id": id if id else _rand_id(8),
|
|
123
131
|
"type": type,
|
|
@@ -126,6 +134,7 @@ def create(
|
|
|
126
134
|
"description": description,
|
|
127
135
|
"credentialId": credential_id,
|
|
128
136
|
"tenantId": tenant_id,
|
|
137
|
+
"subscriptionId": subscription_id,
|
|
129
138
|
"credentials": [
|
|
130
139
|
{
|
|
131
140
|
"clientId": client_id,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2023-2023 VMware Inc.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
import hcs_core.sglib.cli_options as cli
|
|
18
|
+
|
|
19
|
+
import hcs_cli.service.lcm as lcm
|
|
20
|
+
from hcs_cli.cmds.template.expand import expand_impl
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command(hidden=True)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--number",
|
|
26
|
+
"-n",
|
|
27
|
+
type=int,
|
|
28
|
+
required=False,
|
|
29
|
+
default=0,
|
|
30
|
+
help="Number of VMs to expand. Use negative number to shrink.",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--to",
|
|
34
|
+
"-t",
|
|
35
|
+
type=int,
|
|
36
|
+
required=False,
|
|
37
|
+
default=0,
|
|
38
|
+
help="Expected size of template.",
|
|
39
|
+
)
|
|
40
|
+
@click.argument("template_id", type=str, required=False)
|
|
41
|
+
@cli.org_id
|
|
42
|
+
@cli.wait
|
|
43
|
+
def expand(number: int, to: int, template_id: str, org: str, wait: str):
|
|
44
|
+
"""Update an existing template"""
|
|
45
|
+
|
|
46
|
+
return expand_impl(number, to, template_id, org, wait, lcm)
|
hcs_cli/cmds/lcm/vm/delete.py
CHANGED
|
@@ -24,8 +24,9 @@ from hcs_cli.support.param_util import parse_vm_path
|
|
|
24
24
|
@click.argument("vm_path", type=str, required=False)
|
|
25
25
|
@cli.org_id
|
|
26
26
|
@cli.confirm
|
|
27
|
+
@cli.force
|
|
27
28
|
@cli.wait
|
|
28
|
-
def delete(vm_path: str, org: str, confirm: bool, wait: str, **kwargs):
|
|
29
|
+
def delete(vm_path: str, org: str, confirm: bool, force: bool, wait: str, **kwargs):
|
|
29
30
|
"""Delete VM"""
|
|
30
31
|
template_id, vm_id = parse_vm_path(vm_path)
|
|
31
32
|
org_id = cli.get_org_id(org)
|
|
@@ -37,7 +38,7 @@ def delete(vm_path: str, org: str, confirm: bool, wait: str, **kwargs):
|
|
|
37
38
|
if not confirm:
|
|
38
39
|
click.confirm(f"Delete VM {template_id}/{vm_id}?", abort=True)
|
|
39
40
|
|
|
40
|
-
vm.delete(template_id, vm_id, org_id, **kwargs)
|
|
41
|
+
vm.delete(template_id, vm_id, org_id, force=force, **kwargs)
|
|
41
42
|
|
|
42
43
|
if wait == "0":
|
|
43
44
|
return
|
hcs_cli/cmds/scm/plan.py
CHANGED
|
@@ -16,11 +16,13 @@ limitations under the License.
|
|
|
16
16
|
import json
|
|
17
17
|
import re
|
|
18
18
|
import sys
|
|
19
|
+
from datetime import datetime, timedelta
|
|
19
20
|
from os import path
|
|
20
21
|
|
|
21
22
|
import click
|
|
22
23
|
import hcs_core.sglib.cli_options as cli
|
|
23
|
-
|
|
24
|
+
import yumako
|
|
25
|
+
from hcs_core.ctxp import data_util, recent, util
|
|
24
26
|
from hcs_core.sglib.client_util import wait_for_res_status
|
|
25
27
|
|
|
26
28
|
import hcs_cli.service.scm as scm
|
|
@@ -33,6 +35,98 @@ def plan():
|
|
|
33
35
|
pass
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def _get_next_slot_name():
|
|
39
|
+
"""
|
|
40
|
+
Get the current and next half-hour aligned slot names and time delta based on current UTC time.
|
|
41
|
+
|
|
42
|
+
Slots are half-hour aligned (HH:00 or HH:30).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
tuple: (current_slot_name, next_slot_name, timedelta_to_next_slot)
|
|
46
|
+
where slot names are in format 'weekday/HH:MM' (e.g., 'monday/18:00')
|
|
47
|
+
and timedelta_to_next_slot is a timedelta object
|
|
48
|
+
"""
|
|
49
|
+
current_time_utc = datetime.utcnow()
|
|
50
|
+
|
|
51
|
+
# Weekday names mapping (0=Monday, 6=Sunday in Python's weekday())
|
|
52
|
+
weekday_names = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
|
53
|
+
|
|
54
|
+
# Get current half-hour aligned slot (round down)
|
|
55
|
+
current_minute = current_time_utc.minute
|
|
56
|
+
if current_minute < 30:
|
|
57
|
+
# Current slot is this hour:00
|
|
58
|
+
current_slot_time = current_time_utc.replace(minute=0, second=0, microsecond=0)
|
|
59
|
+
else:
|
|
60
|
+
# Current slot is this hour:30
|
|
61
|
+
current_slot_time = current_time_utc.replace(minute=30, second=0, microsecond=0)
|
|
62
|
+
|
|
63
|
+
current_weekday_name = weekday_names[current_slot_time.weekday()]
|
|
64
|
+
current_time_str = current_slot_time.strftime("%H:%M")
|
|
65
|
+
current_slot_name = f"{current_weekday_name}/{current_time_str}"
|
|
66
|
+
|
|
67
|
+
# Get next half-hour aligned slot
|
|
68
|
+
if current_minute < 30:
|
|
69
|
+
# Next slot is this hour:30
|
|
70
|
+
next_time = current_time_utc.replace(minute=30, second=0, microsecond=0)
|
|
71
|
+
else:
|
|
72
|
+
# Next slot is next hour:00
|
|
73
|
+
next_time = current_time_utc + timedelta(hours=1)
|
|
74
|
+
next_time = next_time.replace(minute=0, second=0, microsecond=0)
|
|
75
|
+
|
|
76
|
+
next_weekday_name = weekday_names[next_time.weekday()]
|
|
77
|
+
next_time_str = next_time.strftime("%H:%M")
|
|
78
|
+
next_slot_name = f"{next_weekday_name}/{next_time_str}"
|
|
79
|
+
|
|
80
|
+
timedelta_to_next = next_time - current_time_utc
|
|
81
|
+
|
|
82
|
+
return current_slot_name, next_slot_name, timedelta_to_next
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _format_scm_plan_task_table(data):
|
|
86
|
+
fields_mapping = {
|
|
87
|
+
"location": "Location",
|
|
88
|
+
"_slot": "Slot",
|
|
89
|
+
"_timeCreatedStale": "Time Created",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
current_slot_name, next_slot_name, timedelta_to_next = _get_next_slot_name()
|
|
93
|
+
time_str_to_next = yumako.time.display(timedelta_to_next.total_seconds())
|
|
94
|
+
|
|
95
|
+
for d in data:
|
|
96
|
+
d["_timeCreatedStale"] = yumako.time.stale(d["timeCreated"] / 1000)
|
|
97
|
+
meta = d["meta"]
|
|
98
|
+
input = d["input"]
|
|
99
|
+
my_slot_name = meta["scm_plan_day"] + "/" + meta["scm_plan_slot"]
|
|
100
|
+
d["_slot"] = my_slot_name
|
|
101
|
+
|
|
102
|
+
if d["worker"] == "com.vmware.horizon.sg.scm.task.CapacityOptimization":
|
|
103
|
+
d["_idealCapacity"] = input["idealCapacity"]
|
|
104
|
+
d["_forecastCapacity"] = input["forecastCapacity"]
|
|
105
|
+
d["_taskKey"] = d["key"]
|
|
106
|
+
|
|
107
|
+
if my_slot_name == next_slot_name:
|
|
108
|
+
d["_nextExecution"] = click.style(f"In {time_str_to_next}", fg="bright_blue")
|
|
109
|
+
else:
|
|
110
|
+
d["_nextExecution"] = ""
|
|
111
|
+
fields_mapping["_nextExecution"] = "Next"
|
|
112
|
+
|
|
113
|
+
if my_slot_name == current_slot_name:
|
|
114
|
+
# d['_lastExecutedStale'] = 'TODO'
|
|
115
|
+
# d['_lastExecutionStatus'] = 'TODO'
|
|
116
|
+
# fields_mapping['_lastExecutedStale'] = 'Executed'
|
|
117
|
+
# fields_mapping['_lastExecutionStatus'] = 'Status'
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
fields_mapping["_idealCapacity"] = "Ideal"
|
|
121
|
+
fields_mapping["_forecastCapacity"] = "Forecast"
|
|
122
|
+
fields_mapping["_taskKey"] = "Task Key"
|
|
123
|
+
else:
|
|
124
|
+
# TODO other worker types
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
return util.format_table(data, fields_mapping)
|
|
128
|
+
|
|
129
|
+
|
|
36
130
|
@plan.command
|
|
37
131
|
@click.option("--template", help="Filter plan by template.")
|
|
38
132
|
@click.option("--task", help="Filter plan by task class name.")
|
|
@@ -99,7 +193,7 @@ def delete(org: str, name: str, confirm: bool, **kwargs):
|
|
|
99
193
|
|
|
100
194
|
@plan.command
|
|
101
195
|
@cli.org_id
|
|
102
|
-
@cli.limit
|
|
196
|
+
@cli.limit(default=336)
|
|
103
197
|
@click.option("--day", required=False, help="Search by day-identifier. Example: Monday")
|
|
104
198
|
@click.option("--time", required=False, help="Search by time. Example: 13:30")
|
|
105
199
|
@click.option("--slot", required=False, help="Search by time slot. Example: Mon/13:30")
|
|
@@ -111,6 +205,7 @@ def delete(org: str, name: str, confirm: bool, **kwargs):
|
|
|
111
205
|
help="Search by task state, as comma-separated values. E.g. 'init,running,success,error', or 'all'.",
|
|
112
206
|
)
|
|
113
207
|
@click.argument("name", required=False)
|
|
208
|
+
@cli.formatter(_format_scm_plan_task_table)
|
|
114
209
|
def tasks(org: str, limit: int, day: str, time: str, slot: str, name: str, state: str, **kwargs):
|
|
115
210
|
"""Get tasks of a named calendar plan."""
|
|
116
211
|
org = cli.get_org_id(org)
|
|
@@ -134,6 +229,40 @@ def tasks(org: str, limit: int, day: str, time: str, slot: str, name: str, state
|
|
|
134
229
|
ret = scm.plan.tasks(org_id=org, id=name, limit=limit, day=day, slot=time, states=state, **kwargs)
|
|
135
230
|
if ret is None:
|
|
136
231
|
return "", 1
|
|
232
|
+
|
|
233
|
+
# sort tasks by slot.
|
|
234
|
+
for t in ret:
|
|
235
|
+
meta = t["meta"]
|
|
236
|
+
d = meta["scm_plan_day"]
|
|
237
|
+
if d == "sunday":
|
|
238
|
+
d = "0"
|
|
239
|
+
elif d == "monday":
|
|
240
|
+
d = "1"
|
|
241
|
+
elif d == "tuesday":
|
|
242
|
+
d = "2"
|
|
243
|
+
elif d == "wednesday":
|
|
244
|
+
d = "3"
|
|
245
|
+
elif d == "thursday":
|
|
246
|
+
d = "4"
|
|
247
|
+
elif d == "friday":
|
|
248
|
+
d = "5"
|
|
249
|
+
elif d == "saturday":
|
|
250
|
+
d = "6"
|
|
251
|
+
else:
|
|
252
|
+
raise ValueError("Invalid day name in task meta: " + d)
|
|
253
|
+
t["_slot"] = d + "/" + meta["scm_plan_slot"]
|
|
254
|
+
ret = sorted(ret, key=lambda x: x["_slot"])
|
|
255
|
+
for t in ret:
|
|
256
|
+
del t["_slot"]
|
|
257
|
+
|
|
258
|
+
# identify the current task result
|
|
259
|
+
# current_slot_name, next_slot_name, timedelta_to_next = _get_next_slot_name()
|
|
260
|
+
# for t in ret:
|
|
261
|
+
# meta = t['meta']
|
|
262
|
+
# my_slot_name = meta['scm_plan_day'] + '/' + meta['scm_plan_slot']
|
|
263
|
+
# if current_slot_name == my_slot_name:
|
|
264
|
+
# break
|
|
265
|
+
|
|
137
266
|
return ret
|
|
138
267
|
|
|
139
268
|
|
|
@@ -343,7 +472,6 @@ def run(org: str, name: str, slot: str, config: str, wait: str, **kwargs):
|
|
|
343
472
|
|
|
344
473
|
|
|
345
474
|
def _wait_for_task(org_id: str, name: str, task_key: str, timeout: str):
|
|
346
|
-
|
|
347
475
|
return wait_for_res_status(
|
|
348
476
|
resource_name=name + "/" + task_key,
|
|
349
477
|
fn_get=lambda: scm.plan.get_task(org_id, name, task_key),
|
hcs_cli/cmds/task.py
CHANGED
|
@@ -13,8 +13,6 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import json
|
|
17
|
-
|
|
18
16
|
import click
|
|
19
17
|
import hcs_core.sglib.cli_options as cli
|
|
20
18
|
import yumako
|
|
@@ -29,9 +27,9 @@ def _format_task_table(data):
|
|
|
29
27
|
schedule = d.get("schedule")
|
|
30
28
|
if schedule:
|
|
31
29
|
if schedule.get("intervalMs"):
|
|
32
|
-
recurring = f
|
|
30
|
+
recurring = f"Every {yumako.time.display(schedule['intervalMs'] / 1000)}"
|
|
33
31
|
elif schedule.get("cronExpression"):
|
|
34
|
-
recurring = f
|
|
32
|
+
recurring = f"{schedule['cronExpression']}"
|
|
35
33
|
else:
|
|
36
34
|
recurring = "<No>"
|
|
37
35
|
else:
|
hcs_cli/cmds/template/expand.py
CHANGED
|
@@ -27,39 +27,84 @@ import hcs_cli.service.admin as admin
|
|
|
27
27
|
"-n",
|
|
28
28
|
type=int,
|
|
29
29
|
required=False,
|
|
30
|
-
default=
|
|
30
|
+
default=0,
|
|
31
31
|
help="Number of VMs to expand. Use negative number to shrink.",
|
|
32
32
|
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--to",
|
|
35
|
+
"-t",
|
|
36
|
+
type=int,
|
|
37
|
+
required=False,
|
|
38
|
+
default=0,
|
|
39
|
+
help="Expected size of template.",
|
|
40
|
+
)
|
|
33
41
|
@click.argument("template_id", type=str, required=False)
|
|
34
42
|
@cli.org_id
|
|
35
43
|
@cli.wait
|
|
36
|
-
def expand(number: int, template_id: str, org: str, wait: str
|
|
44
|
+
def expand(number: int, to: int, template_id: str, org: str, wait: str):
|
|
37
45
|
"""Update an existing template"""
|
|
46
|
+
return expand_impl(number, to, template_id, org, wait, admin)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def expand_impl(number: int, to: int, template_id: str, org: str, wait: str, service):
|
|
50
|
+
if to != 0 and number != 0:
|
|
51
|
+
return "Specify either --to or --number, not both.", 1
|
|
52
|
+
if to < 0:
|
|
53
|
+
return "--to must be non-negative.", 1
|
|
54
|
+
if to > 5000:
|
|
55
|
+
return "--to exceeds maximum limit of 5000.", 1
|
|
38
56
|
|
|
39
57
|
org_id = cli.get_org_id(org)
|
|
40
58
|
|
|
41
59
|
template_id = recent.require("template", template_id)
|
|
42
|
-
template =
|
|
60
|
+
template = service.template.get(template_id, org_id)
|
|
43
61
|
|
|
44
62
|
if not template:
|
|
45
|
-
return "Template not found: " + template_id
|
|
63
|
+
return "Template not found: " + template_id, 1
|
|
46
64
|
|
|
47
65
|
spare_policy = template.get("sparePolicy", {})
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"limit": spare_policy.get("limit", 0) + number,
|
|
53
|
-
}
|
|
66
|
+
new_spare_policy = {
|
|
67
|
+
"min": spare_policy.get("min", 0),
|
|
68
|
+
"max": spare_policy.get("max", 0),
|
|
69
|
+
"limit": spare_policy.get("limit", 0),
|
|
54
70
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
|
|
72
|
+
if to != 0:
|
|
73
|
+
new_spare_policy["min"] = to
|
|
74
|
+
new_spare_policy["max"] = to
|
|
75
|
+
new_spare_policy["limit"] = to
|
|
76
|
+
else:
|
|
77
|
+
if number == 0:
|
|
78
|
+
number = 1 # default to expand by 1
|
|
79
|
+
new_spare_policy["min"] += number
|
|
80
|
+
new_spare_policy["max"] += number
|
|
81
|
+
new_spare_policy["limit"] += number
|
|
82
|
+
|
|
83
|
+
if new_spare_policy["limit"] < 0:
|
|
84
|
+
new_spare_policy["limit"] = 0
|
|
85
|
+
elif new_spare_policy["limit"] > 5000:
|
|
86
|
+
new_spare_policy["limit"] = 5000
|
|
87
|
+
|
|
88
|
+
if new_spare_policy["min"] < 0:
|
|
89
|
+
new_spare_policy["min"] = 0
|
|
90
|
+
elif new_spare_policy["min"] > new_spare_policy["limit"]:
|
|
91
|
+
new_spare_policy["min"] = new_spare_policy["limit"]
|
|
92
|
+
if new_spare_policy["max"] < 0:
|
|
93
|
+
new_spare_policy["max"] = 0
|
|
94
|
+
elif new_spare_policy["max"] > new_spare_policy["limit"]:
|
|
95
|
+
new_spare_policy["max"] = new_spare_policy["limit"]
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
new_spare_policy["min"] == spare_policy.get("min", 0)
|
|
99
|
+
and new_spare_policy["max"] == spare_policy.get("max", 0)
|
|
100
|
+
and new_spare_policy["limit"] == spare_policy.get("limit", 0)
|
|
101
|
+
):
|
|
102
|
+
# no change
|
|
103
|
+
ret = template
|
|
104
|
+
else:
|
|
105
|
+
patch = {"sparePolicy": new_spare_policy}
|
|
106
|
+
ret = service.template.patch(template_id, org_id, patch)
|
|
107
|
+
|
|
63
108
|
if wait != "0":
|
|
64
|
-
ret =
|
|
109
|
+
ret = service.template.wait_for_ready(template_id, org_id, duration.to_seconds(wait))
|
|
65
110
|
return ret
|
|
@@ -111,8 +111,8 @@ def list_usage(org: str, **kwargs):
|
|
|
111
111
|
"addedVmHours": summary.get("addedVmHours"),
|
|
112
112
|
"offloadVmHours": summary.get("offloadVmHours"),
|
|
113
113
|
"reducedVmHours": summary.get("reducedVmHours"),
|
|
114
|
-
"historyVmUtilizationPercent": f
|
|
115
|
-
"predictionVmUtilizationPercent": f
|
|
114
|
+
"historyVmUtilizationPercent": f"{summary.get('historyVmUtilizationPercent', 0)}%",
|
|
115
|
+
"predictionVmUtilizationPercent": f"{summary.get('predictionVmUtilizationPercent', 0)}%",
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
ret2.append(item)
|
hcs_cli/cmds/template/update.py
CHANGED
|
@@ -43,7 +43,7 @@ def update(template_id: str, update, org: str, wait: str, **kwargs):
|
|
|
43
43
|
template = admin.template.get(template_id, org_id)
|
|
44
44
|
|
|
45
45
|
if not template:
|
|
46
|
-
return "Template not found: " + template_id
|
|
46
|
+
return "Template not found: " + template_id, 1
|
|
47
47
|
|
|
48
48
|
allowed_fields = ["name", "description", "powerPolicy", "sparePolicy", "applicationProperties", "flags"]
|
|
49
49
|
|
|
@@ -52,7 +52,7 @@ def update(template_id: str, update, org: str, wait: str, **kwargs):
|
|
|
52
52
|
if not patch:
|
|
53
53
|
return template
|
|
54
54
|
|
|
55
|
-
ret = admin.template.
|
|
55
|
+
ret = admin.template.patch(template_id, org_id, patch)
|
|
56
56
|
if wait != "0":
|
|
57
57
|
ret = admin.template.wait_for_ready(template_id, org_id, duration.to_seconds(wait))
|
|
58
58
|
return ret
|
hcs_cli/cmds/template/usage.py
CHANGED
|
@@ -13,6 +13,7 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import json
|
|
16
17
|
import os
|
|
17
18
|
import tempfile
|
|
18
19
|
import time
|
|
@@ -32,17 +33,28 @@ def _timestamp_to_date(timestamp: int):
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@click.command(hidden=True)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--local-plan-file",
|
|
38
|
+
type=str,
|
|
39
|
+
required=False,
|
|
40
|
+
help="Instead of reading from API, read from local file for the template usage data. Debug only.",
|
|
41
|
+
)
|
|
35
42
|
@click.argument("id", type=str, required=False)
|
|
36
43
|
@cli.org_id
|
|
37
|
-
def usage(id: str, org: str, **kwargs):
|
|
44
|
+
def usage(id: str, org: str, local_plan_file: str = None, **kwargs):
|
|
38
45
|
"""Show usage visualization"""
|
|
39
46
|
|
|
40
47
|
org_id = cli.get_org_id(org)
|
|
41
48
|
id = recent.require("template", id)
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
if local_plan_file:
|
|
51
|
+
with open(local_plan_file, "r") as f:
|
|
52
|
+
plan_data = json.load(f)
|
|
53
|
+
usage = plan_data["meta"]
|
|
54
|
+
else:
|
|
55
|
+
usage = scm.template_usage(org_id, id)
|
|
56
|
+
if not usage:
|
|
57
|
+
return "No usage data found", 1
|
|
46
58
|
|
|
47
59
|
x_axis = []
|
|
48
60
|
consumed_capacity = []
|
|
@@ -61,8 +73,9 @@ def usage(id: str, org: str, **kwargs):
|
|
|
61
73
|
x_axis.append(_timestamp_to_date(t))
|
|
62
74
|
max_capacity = history["maxCapacity"][i]
|
|
63
75
|
min_free = history["minFree"][i]
|
|
64
|
-
consumed_capacity.append(max_capacity - min_free)
|
|
65
|
-
|
|
76
|
+
# consumed_capacity.append(max_capacity - min_free)
|
|
77
|
+
consumed_capacity.append(history["poweredOnAssignedVms"][i])
|
|
78
|
+
spare_capacity.append(max_capacity - consumed_capacity[-1])
|
|
66
79
|
no_spare_error.append(history["noSpare"][i])
|
|
67
80
|
|
|
68
81
|
start_timestamp = prediction["startTimestamp"]
|
|
@@ -72,7 +85,7 @@ def usage(id: str, org: str, **kwargs):
|
|
|
72
85
|
max_capacity = prediction["maxCapacity"][i]
|
|
73
86
|
min_free = prediction["minFree"][i]
|
|
74
87
|
ideal_capacity = prediction["idealCapacity"][i]
|
|
75
|
-
consumed_capacity_predicated.append(ideal_capacity
|
|
88
|
+
consumed_capacity_predicated.append(ideal_capacity)
|
|
76
89
|
spare_capacity_predicated.append(min_free)
|
|
77
90
|
optimized_capacity.append(ideal_capacity)
|
|
78
91
|
no_spare_error_predicated.append(prediction["noSpare"][i])
|
hcs_cli/cmds/vm/delete.py
CHANGED
|
@@ -23,10 +23,11 @@ from hcs_cli.support.param_util import parse_vm_path
|
|
|
23
23
|
|
|
24
24
|
@click.command()
|
|
25
25
|
@click.argument("vm_path", type=str, required=False)
|
|
26
|
+
@cli.force
|
|
26
27
|
@cli.org_id
|
|
27
28
|
@cli.confirm
|
|
28
29
|
@cli.wait
|
|
29
|
-
def delete(vm_path: str, org: str, confirm: bool, wait: str, **kwargs):
|
|
30
|
+
def delete(vm_path: str, org: str, confirm: bool, force: bool, wait: str, **kwargs):
|
|
30
31
|
"""Delete a VM by ID"""
|
|
31
32
|
org_id = cli.get_org_id(org)
|
|
32
33
|
template_id, vm_id = parse_vm_path(vm_path)
|
|
@@ -41,7 +42,7 @@ def delete(vm_path: str, org: str, confirm: bool, wait: str, **kwargs):
|
|
|
41
42
|
if not confirm:
|
|
42
43
|
click.confirm(f"Delete vm {existing['id']} from template {template['name']} ({template['id']})?", abort=True)
|
|
43
44
|
|
|
44
|
-
vm.delete()
|
|
45
|
+
vm.delete(force=force)
|
|
45
46
|
|
|
46
47
|
if wait == "0":
|
|
47
48
|
return
|