hcs-cli 0.1.317__py3-none-any.whl → 0.1.319__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 +1 -1
- hcs_cli/cmds/advisor/html_utils.py +30 -26
- hcs_cli/cmds/advisor/recommendation_engine.py +7 -10
- hcs_cli/cmds/api.py +5 -3
- hcs_cli/cmds/daas/tenant/plan.py +1 -1
- hcs_cli/cmds/debug/start.py +2 -3
- 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/helper/license_info.py +36 -12
- 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/onboard.py +35 -1
- hcs_cli/cmds/dev/util/mqtt_helper.py +0 -1
- hcs_cli/cmds/hoc/search.py +39 -9
- hcs_cli/cmds/hst/clean.py +2 -1
- hcs_cli/cmds/inventory/assign.py +72 -28
- hcs_cli/cmds/inventory/deassign.py +35 -18
- hcs_cli/cmds/inventory/logoff.py +86 -8
- hcs_cli/cmds/inventory/session.py +2 -2
- hcs_cli/cmds/scm/operator.py +2 -2
- hcs_cli/cmds/scm/plan.py +131 -3
- hcs_cli/cmds/task.py +2 -4
- hcs_cli/cmds/template/expand.py +65 -0
- hcs_cli/cmds/template/list_usage.py +2 -2
- hcs_cli/cmds/template/usage.py +20 -7
- hcs_cli/cmds/vm/list.py +0 -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-nt.json +64 -66
- 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 +6 -1
- hcs_cli/service/hoc/diagnostic.py +0 -3
- hcs_cli/service/inventory/__init__.py +1 -1
- hcs_cli/service/inventory/session.py +23 -7
- hcs_cli/service/lcm/vm.py +0 -1
- hcs_cli/service/task.py +0 -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-0.1.317.dist-info → hcs_cli-0.1.319.dist-info}/METADATA +24 -17
- {hcs_cli-0.1.317.dist-info → hcs_cli-0.1.319.dist-info}/RECORD +112 -108
- {hcs_cli-0.1.317.dist-info → hcs_cli-0.1.319.dist-info}/WHEEL +0 -0
- {hcs_cli-0.1.317.dist-info → hcs_cli-0.1.319.dist-info}/entry_points.txt +0 -0
hcs_cli/cmds/inventory/assign.py
CHANGED
|
@@ -13,47 +13,91 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import json
|
|
17
|
-
import sys
|
|
18
16
|
import uuid
|
|
19
17
|
|
|
20
18
|
import click
|
|
21
19
|
import hcs_core.sglib.cli_options as cli
|
|
22
20
|
|
|
23
|
-
from hcs_cli.service import auth, inventory, portal
|
|
21
|
+
from hcs_cli.service import admin, auth, inventory, lcm, portal
|
|
24
22
|
from hcs_cli.support.param_util import parse_vm_path
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
@click.command()
|
|
28
26
|
@cli.org_id
|
|
29
|
-
@click.
|
|
30
|
-
|
|
31
|
-
"-f",
|
|
32
|
-
type=click.File("rt"),
|
|
33
|
-
default=sys.stdin,
|
|
34
|
-
help="Specify the template file name. If not specified, STDIN will be used.",
|
|
35
|
-
)
|
|
36
|
-
@click.option("--vm", type=str, required=False)
|
|
37
|
-
def assign(org: str, file, vm: str):
|
|
27
|
+
@click.argument("vm_path", type=str, required=False)
|
|
28
|
+
def assign(org: str, vm_path: str):
|
|
38
29
|
"""Assign user to VM"""
|
|
39
30
|
org_id = cli.get_org_id(org)
|
|
31
|
+
template_id, vm_id = parse_vm_path(vm_path)
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
33
|
+
template = admin.template.get(template_id, org_id)
|
|
34
|
+
if template:
|
|
35
|
+
edge_id = template["edgeDeploymentId"]
|
|
36
|
+
else:
|
|
37
|
+
# try with lcm
|
|
38
|
+
template = lcm.template.get(template_id, org_id)
|
|
39
|
+
edge_id = template["edgeGateway"]["id"]
|
|
40
|
+
if not template:
|
|
41
|
+
return "Template not found: " + template_id, 1
|
|
42
|
+
payload = {
|
|
43
|
+
"id": "dspec1",
|
|
44
|
+
"userId": "user1",
|
|
45
|
+
"clientId": "client-1",
|
|
46
|
+
"entitlementId": "entitlement-1",
|
|
47
|
+
"templateIds": [template_id],
|
|
48
|
+
"allocationPolicy": "ANY",
|
|
49
|
+
"sessionType": "DESKTOP",
|
|
50
|
+
"username": "user1",
|
|
51
|
+
"userPrincipalName": "aduser1@vmwhorizon.com",
|
|
52
|
+
"userSid": "S-1-5-21-2502306595",
|
|
53
|
+
"orgId": org_id,
|
|
54
|
+
"location": template["location"],
|
|
55
|
+
"vmId": vm_id,
|
|
56
|
+
"templateType": template["templateType"],
|
|
57
|
+
"resume": True,
|
|
58
|
+
"clientSessionId": "client-uuid-1",
|
|
59
|
+
}
|
|
60
|
+
# print(json.dumps(payload, indent=4))
|
|
61
|
+
inventory.assignV2(payload)
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
# payload_manual_session = {
|
|
64
|
+
# "templateId": "string",
|
|
65
|
+
# "vmId": "string",
|
|
66
|
+
# "userId": "string",
|
|
67
|
+
# "agentSessionGuid": "string",
|
|
68
|
+
# "agentSessionId": "string",
|
|
69
|
+
# "agentStaticSessionGuid": "string",
|
|
70
|
+
# "sessionType": "DESKTOP",
|
|
71
|
+
# "templateType": "FLOATING",
|
|
72
|
+
# "clientId": "string",
|
|
73
|
+
# "sessionStatus": "string",
|
|
74
|
+
# "lastAssignedTime": "2025-12-16T20:56:08.988Z",
|
|
75
|
+
# "lastLoginTime": "2025-12-16T20:56:08.988Z",
|
|
76
|
+
# "lastStatusUpdateTime": "2025-12-16T20:56:08.988Z",
|
|
77
|
+
# "username": "string",
|
|
78
|
+
# "userPrincipalName": "string",
|
|
79
|
+
# "userSid": "string",
|
|
80
|
+
# "entitlementId": "string",
|
|
81
|
+
# "orgId": "string",
|
|
82
|
+
# "location": "string",
|
|
83
|
+
# "clientSessionId": "UUID string",
|
|
84
|
+
# "agentErrorCode": "AGENT_ERR_FAILURE",
|
|
85
|
+
# "staticCloudSessionId": "UUID string",
|
|
86
|
+
# "hibernated": true,
|
|
87
|
+
# "id": 0,
|
|
88
|
+
# "dspecId": "string"
|
|
89
|
+
# }
|
|
90
|
+
payload_update = {
|
|
91
|
+
"id": "dspec1",
|
|
92
|
+
"agentSessionGuid": "agent-session-guid-1",
|
|
93
|
+
"agentSessionId": "agent-session-1",
|
|
94
|
+
"agentStaticSessionGuid": "agent-static-session-guid-1",
|
|
95
|
+
"sessionStatus": "string",
|
|
96
|
+
"vmId": vm_id,
|
|
97
|
+
"edgeId": edge_id,
|
|
98
|
+
"agentErrorCode": "",
|
|
99
|
+
}
|
|
100
|
+
return inventory.update_session(org_id=org_id, template_id=template_id, payload=payload_update)
|
|
57
101
|
|
|
58
102
|
|
|
59
103
|
@click.command()
|
|
@@ -84,7 +128,7 @@ def bulk_assign(org: str, pool_group: str, pool: str, num_users: int):
|
|
|
84
128
|
|
|
85
129
|
num_users = int(num_users)
|
|
86
130
|
if num_users > len(users["users"]):
|
|
87
|
-
print(f
|
|
131
|
+
print(f"max available users in AD = {len(users['users'])}, set -n less than {len(users['users'])}")
|
|
88
132
|
return
|
|
89
133
|
fu = users["users"][:num_users]
|
|
90
134
|
dspecs = []
|
|
@@ -19,29 +19,46 @@ import sys
|
|
|
19
19
|
import click
|
|
20
20
|
import hcs_core.sglib.cli_options as cli
|
|
21
21
|
|
|
22
|
-
from hcs_cli.service import inventory
|
|
22
|
+
from hcs_cli.service import admin, inventory, lcm
|
|
23
|
+
from hcs_cli.support.param_util import parse_vm_path
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@click.command()
|
|
26
27
|
@cli.org_id
|
|
27
|
-
@click.
|
|
28
|
-
|
|
29
|
-
"-f",
|
|
30
|
-
type=click.File("rt"),
|
|
31
|
-
default=sys.stdin,
|
|
32
|
-
help="Specify the template file name. If not specified, STDIN will be used.",
|
|
33
|
-
)
|
|
34
|
-
def deassign(org: str, file):
|
|
28
|
+
@click.argument("vm_path", type=str, required=False)
|
|
29
|
+
def deassign(org: str, vm_path: str):
|
|
35
30
|
"""Assign user to VM"""
|
|
36
31
|
org_id = cli.get_org_id(org)
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
template_id, vm_id = parse_vm_path(vm_path)
|
|
33
|
+
sessions = inventory.sessions(template_id, vm_id, cli.get_org_id(org))
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
template = admin.template.get(template_id, cli.get_org_id(org))
|
|
36
|
+
if template:
|
|
37
|
+
edge_deployment_id = template["edgeDeploymentId"]
|
|
38
|
+
else:
|
|
39
|
+
# try with lcm
|
|
40
|
+
template = lcm.template.get(template_id, cli.get_org_id(org))
|
|
41
|
+
if not template:
|
|
42
|
+
return "Template not found: " + template_id, 1
|
|
43
|
+
edge_deployment_id = template["edgeGateway"]["id"]
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
all_ret = []
|
|
46
|
+
for session in sessions:
|
|
47
|
+
payload = {
|
|
48
|
+
"vmHubName": template["hdc"]["vmHub"]["name"],
|
|
49
|
+
"edgeDeploymentId": edge_deployment_id,
|
|
50
|
+
"vmId": vm_id,
|
|
51
|
+
"entitlementId": session.get("entitlementId"),
|
|
52
|
+
"agentSessionGuid": session.get("agentSessionGuid"),
|
|
53
|
+
"agentSessionId": session.get("agentSessionId"),
|
|
54
|
+
"agentStaticSessionGuid": session.get("agentStaticSessionGuid"),
|
|
55
|
+
"id": session.get("dspecId"),
|
|
56
|
+
"userId": session.get("userId"),
|
|
57
|
+
"type": "CONNECTION_ENDED",
|
|
58
|
+
}
|
|
59
|
+
ret = inventory.deassign(template_id=template_id, org_id=org_id, payload=payload)
|
|
60
|
+
all_ret.append(ret)
|
|
61
|
+
|
|
62
|
+
if len(all_ret) == 1:
|
|
63
|
+
return all_ret[0]
|
|
64
|
+
return all_ret
|
hcs_cli/cmds/inventory/logoff.py
CHANGED
|
@@ -16,17 +16,95 @@ limitations under the License.
|
|
|
16
16
|
import click
|
|
17
17
|
import hcs_core.sglib.cli_options as cli
|
|
18
18
|
|
|
19
|
-
from hcs_cli.service import inventory
|
|
19
|
+
from hcs_cli.service import admin, inventory, lcm
|
|
20
20
|
from hcs_cli.support.param_util import parse_vm_path
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@click.command()
|
|
24
24
|
@cli.org_id
|
|
25
|
-
@click.argument("vm_path", type=str, required=
|
|
25
|
+
@click.argument("vm_path", type=str, required=False)
|
|
26
26
|
def logoff(org: str, vm_path: str):
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
"""Logoff all sessions on the vm.
|
|
28
|
+
|
|
29
|
+
Example: hcs inventory logoff template_id/vm_id
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
org_id = cli.get_org_id(org)
|
|
33
|
+
template_id, vm_id = parse_vm_path(vm_path)
|
|
34
|
+
sessions = inventory.sessions(template_id, vm_id, cli.get_org_id(org))
|
|
35
|
+
if not sessions:
|
|
36
|
+
return
|
|
37
|
+
# [
|
|
38
|
+
# {
|
|
39
|
+
# "templateId": null,
|
|
40
|
+
# "vmId": "expool000",
|
|
41
|
+
# "userId": "c12b884b-c813-4f73-81f5-df2052de9bc6",
|
|
42
|
+
# "agentSessionGuid": null,
|
|
43
|
+
# "agentSessionId": null,
|
|
44
|
+
# "agentStaticSessionGuid": null,
|
|
45
|
+
# "sessionType": "DESKTOP",
|
|
46
|
+
# "templateType": "FLOATING",
|
|
47
|
+
# "clientId": null,
|
|
48
|
+
# "sessionStatus": "ASSIGNED",
|
|
49
|
+
# "lastAssignedTime": "2025-12-16T18:55:45.078+00:00",
|
|
50
|
+
# "lastLoginTime": null,
|
|
51
|
+
# "lastStatusUpdateTime": "2025-12-16T18:55:45.078+00:00",
|
|
52
|
+
# "username": "u1ad1",
|
|
53
|
+
# "userPrincipalName": "u1ad1@horizonv2dev2.local",
|
|
54
|
+
# "userSid": "S-1-5-21-1840667356-2516272024-4034330832-1104",
|
|
55
|
+
# "releaseSessionOnDeassign": false,
|
|
56
|
+
# "entitlementId": "6941a2bfe79a1b9cef167ad2",
|
|
57
|
+
# "orgId": "7c4f9042-6119-45b6-93f5-24f1a80e2c62",
|
|
58
|
+
# "location": null,
|
|
59
|
+
# "clientSessionId": null,
|
|
60
|
+
# "agentErrorCode": "",
|
|
61
|
+
# "staticCloudSessionId": "dc907d57-85d8-478e-9d0c-028c1acd87f2",
|
|
62
|
+
# "hibernated": null,
|
|
63
|
+
# "id": null,
|
|
64
|
+
# "dspecId": "5722ff4a-7e75-4248-89e9-741d8a7ae91c"
|
|
65
|
+
# }
|
|
66
|
+
# ]
|
|
67
|
+
|
|
68
|
+
template = admin.template.get(template_id, cli.get_org_id(org))
|
|
69
|
+
if template:
|
|
70
|
+
edge_deployment_id = template["edgeDeploymentId"]
|
|
71
|
+
else:
|
|
72
|
+
# try with lcm
|
|
73
|
+
template = lcm.template.get(template_id, cli.get_org_id(org))
|
|
74
|
+
if not template:
|
|
75
|
+
return "Template not found: " + template_id, 1
|
|
76
|
+
edge_deployment_id = template["edgeGateway"]["id"]
|
|
77
|
+
|
|
78
|
+
all_ret = []
|
|
79
|
+
for session in sessions:
|
|
80
|
+
logoff_payload_v1 = {
|
|
81
|
+
"edgeDeploymentId": edge_deployment_id,
|
|
82
|
+
"vmId": vm_id,
|
|
83
|
+
"userId": session.get("userId"),
|
|
84
|
+
"entitlementId": session.get("entitlementId"),
|
|
85
|
+
"vmHubName": template["hdc"]["vmHub"]["name"],
|
|
86
|
+
"sessionType": session.get("sessionType"),
|
|
87
|
+
}
|
|
88
|
+
ret1 = inventory.logoff(template_id=template_id, org_id=org_id, payload=logoff_payload_v1)
|
|
89
|
+
# print("PAYLOADv1: ", logoff_payload_v1)
|
|
90
|
+
# print("RETv1: ", ret1)
|
|
91
|
+
all_ret.append(ret1)
|
|
92
|
+
|
|
93
|
+
logoff_payload_v2 = [
|
|
94
|
+
{
|
|
95
|
+
"agentSessionGuid": session.get("agentSessionGuid"),
|
|
96
|
+
"userId": session.get("userId"),
|
|
97
|
+
"sessionType": session.get("sessionType"),
|
|
98
|
+
"vmHubName": template["hdc"]["vmHub"]["name"],
|
|
99
|
+
"edgeDeploymentId": edge_deployment_id,
|
|
100
|
+
"vmId": vm_id,
|
|
101
|
+
"dspecId": session.get("dspecId"),
|
|
102
|
+
"dtemplateId": template_id,
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
ret2 = inventory.logoffV2(org_id=org_id, payload=logoff_payload_v2)
|
|
106
|
+
# print("PAYLOADv2: ", logoff_payload_v2)
|
|
107
|
+
# print("RETv2: ", ret2)
|
|
108
|
+
all_ret.append(ret2)
|
|
109
|
+
|
|
110
|
+
return all_ret
|
|
@@ -16,7 +16,7 @@ limitations under the License.
|
|
|
16
16
|
import click
|
|
17
17
|
import hcs_core.sglib.cli_options as cli
|
|
18
18
|
|
|
19
|
-
from hcs_cli.service import inventory
|
|
19
|
+
from hcs_cli.service import admin, inventory
|
|
20
20
|
from hcs_cli.support.param_util import parse_vm_path
|
|
21
21
|
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ def session():
|
|
|
27
27
|
|
|
28
28
|
@session.command()
|
|
29
29
|
@cli.org_id
|
|
30
|
-
@click.argument("vm_path", type=str, required=
|
|
30
|
+
@click.argument("vm_path", type=str, required=False)
|
|
31
31
|
def list(org: str, vm_path: str):
|
|
32
32
|
"""List sessions"""
|
|
33
33
|
template, vm = parse_vm_path(vm_path)
|
hcs_cli/cmds/scm/operator.py
CHANGED
|
@@ -60,8 +60,8 @@ def _print_error(text: str):
|
|
|
60
60
|
|
|
61
61
|
def _print_human_readable_log(d: TaskModel):
|
|
62
62
|
ret = d.log
|
|
63
|
-
time_started = ret.
|
|
64
|
-
time_completed = ret.
|
|
63
|
+
time_started = ret.timeStarted
|
|
64
|
+
time_completed = ret.timeCompleted
|
|
65
65
|
|
|
66
66
|
if time_completed:
|
|
67
67
|
delta = timedelta(milliseconds=time_completed - time_started)
|
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:
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
import hcs_core.util.duration as duration
|
|
19
|
+
from hcs_core.ctxp import recent
|
|
20
|
+
|
|
21
|
+
import hcs_cli.service.admin as admin
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command(hidden=True)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--number",
|
|
27
|
+
"-n",
|
|
28
|
+
type=int,
|
|
29
|
+
required=False,
|
|
30
|
+
default=1,
|
|
31
|
+
help="Number of VMs to expand. Use negative number to shrink.",
|
|
32
|
+
)
|
|
33
|
+
@click.argument("template_id", type=str, required=False)
|
|
34
|
+
@cli.org_id
|
|
35
|
+
@cli.wait
|
|
36
|
+
def expand(number: int, template_id: str, org: str, wait: str, **kwargs):
|
|
37
|
+
"""Update an existing template"""
|
|
38
|
+
|
|
39
|
+
org_id = cli.get_org_id(org)
|
|
40
|
+
|
|
41
|
+
template_id = recent.require("template", template_id)
|
|
42
|
+
template = admin.template.get(template_id, org_id)
|
|
43
|
+
|
|
44
|
+
if not template:
|
|
45
|
+
return "Template not found: " + template_id
|
|
46
|
+
|
|
47
|
+
spare_policy = template.get("sparePolicy", {})
|
|
48
|
+
patch = {
|
|
49
|
+
"sparePolicy": {
|
|
50
|
+
"min": spare_policy.get("min", 0) + number,
|
|
51
|
+
"max": spare_policy.get("max", 0) + number,
|
|
52
|
+
"limit": spare_policy.get("limit", 0) + number,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if patch["sparePolicy"]["min"] < 0:
|
|
56
|
+
patch["sparePolicy"]["min"] = 0
|
|
57
|
+
if patch["sparePolicy"]["max"] < 0:
|
|
58
|
+
patch["sparePolicy"]["max"] = 0
|
|
59
|
+
if patch["sparePolicy"]["limit"] < 0:
|
|
60
|
+
patch["sparePolicy"]["limit"] = 0
|
|
61
|
+
|
|
62
|
+
ret = admin.template.update(template_id, org_id, patch)
|
|
63
|
+
if wait != "0":
|
|
64
|
+
ret = admin.template.wait_for_ready(template_id, org_id, duration.to_seconds(wait))
|
|
65
|
+
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/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])
|