@calltelemetry/ct-lab-mcp 0.1.4 → 0.2.0
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.
- package/dist/bin/ct-lab-mcp.d.ts.map +1 -1
- package/dist/bin/ct-lab-mcp.js +21 -7
- package/dist/bin/ct-lab-mcp.js.map +1 -1
- package/dist/src/appliance/commands.d.ts.map +1 -1
- package/dist/src/appliance/commands.js +24 -0
- package/dist/src/appliance/commands.js.map +1 -1
- package/dist/src/appliance/executor.d.ts.map +1 -1
- package/dist/src/appliance/executor.js +7 -1
- package/dist/src/appliance/executor.js.map +1 -1
- package/dist/src/appliance/mock.d.ts +6 -0
- package/dist/src/appliance/mock.d.ts.map +1 -1
- package/dist/src/appliance/mock.js +27 -6
- package/dist/src/appliance/mock.js.map +1 -1
- package/dist/src/appliance/types.d.ts +20 -1
- package/dist/src/appliance/types.d.ts.map +1 -1
- package/dist/src/appliance/types.js.map +1 -1
- package/dist/src/credentials/errors.d.ts +16 -0
- package/dist/src/credentials/errors.d.ts.map +1 -0
- package/dist/src/credentials/errors.js +21 -0
- package/dist/src/credentials/errors.js.map +1 -0
- package/dist/src/credentials/resolver.d.ts +16 -0
- package/dist/src/credentials/resolver.d.ts.map +1 -1
- package/dist/src/credentials/resolver.js +104 -10
- package/dist/src/credentials/resolver.js.map +1 -1
- package/dist/src/credentials/types.d.ts +8 -0
- package/dist/src/credentials/types.d.ts.map +1 -1
- package/dist/src/diagnostics/collector.d.ts +37 -0
- package/dist/src/diagnostics/collector.d.ts.map +1 -0
- package/dist/src/diagnostics/collector.js +452 -0
- package/dist/src/diagnostics/collector.js.map +1 -0
- package/dist/src/diagnostics/types.d.ts +103 -0
- package/dist/src/diagnostics/types.d.ts.map +1 -0
- package/dist/src/diagnostics/types.js +8 -0
- package/dist/src/diagnostics/types.js.map +1 -0
- package/dist/src/hypervisors/esxi/client.d.ts +1 -0
- package/dist/src/hypervisors/esxi/client.d.ts.map +1 -1
- package/dist/src/hypervisors/esxi/client.js +15 -3
- package/dist/src/hypervisors/esxi/client.js.map +1 -1
- package/dist/src/hypervisors/esxi/mock.d.ts +1 -0
- package/dist/src/hypervisors/esxi/mock.d.ts.map +1 -1
- package/dist/src/hypervisors/esxi/mock.js +4 -0
- package/dist/src/hypervisors/esxi/mock.js.map +1 -1
- package/dist/src/hypervisors/manager.d.ts.map +1 -1
- package/dist/src/hypervisors/manager.js +37 -3
- package/dist/src/hypervisors/manager.js.map +1 -1
- package/dist/src/hypervisors/proxmox/client.d.ts +18 -1
- package/dist/src/hypervisors/proxmox/client.d.ts.map +1 -1
- package/dist/src/hypervisors/proxmox/client.js +563 -3
- package/dist/src/hypervisors/proxmox/client.js.map +1 -1
- package/dist/src/hypervisors/proxmox/mock.d.ts +1 -0
- package/dist/src/hypervisors/proxmox/mock.d.ts.map +1 -1
- package/dist/src/hypervisors/proxmox/mock.js +4 -0
- package/dist/src/hypervisors/proxmox/mock.js.map +1 -1
- package/dist/src/hypervisors/types.d.ts +159 -1
- package/dist/src/hypervisors/types.d.ts.map +1 -1
- package/dist/src/proxmox/mock.d.ts +2 -1
- package/dist/src/proxmox/mock.d.ts.map +1 -1
- package/dist/src/proxmox/mock.js +202 -0
- package/dist/src/proxmox/mock.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +2 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/slot/manager.d.ts +26 -1
- package/dist/src/slot/manager.d.ts.map +1 -1
- package/dist/src/slot/manager.js +278 -1
- package/dist/src/slot/manager.js.map +1 -1
- package/dist/src/slot/types.d.ts +49 -0
- package/dist/src/slot/types.d.ts.map +1 -1
- package/dist/src/slot/types.js +18 -0
- package/dist/src/slot/types.js.map +1 -1
- package/dist/src/tools/credential-status.d.ts +29 -0
- package/dist/src/tools/credential-status.d.ts.map +1 -0
- package/dist/src/tools/credential-status.js +81 -0
- package/dist/src/tools/credential-status.js.map +1 -0
- package/dist/src/tools/cube-provision.d.ts +288 -0
- package/dist/src/tools/cube-provision.d.ts.map +1 -0
- package/dist/src/tools/cube-provision.js +892 -0
- package/dist/src/tools/cube-provision.js.map +1 -0
- package/dist/src/tools/cube-tools.d.ts +137 -0
- package/dist/src/tools/cube-tools.d.ts.map +1 -0
- package/dist/src/tools/cube-tools.js +292 -0
- package/dist/src/tools/cube-tools.js.map +1 -0
- package/dist/src/tools/cucm-emulator-tool.d.ts +85 -0
- package/dist/src/tools/cucm-emulator-tool.d.ts.map +1 -0
- package/dist/src/tools/cucm-emulator-tool.js +191 -0
- package/dist/src/tools/cucm-emulator-tool.js.map +1 -0
- package/dist/src/tools/cucm-trunk.d.ts +165 -0
- package/dist/src/tools/cucm-trunk.d.ts.map +1 -0
- package/dist/src/tools/cucm-trunk.js +354 -0
- package/dist/src/tools/cucm-trunk.js.map +1 -0
- package/dist/src/tools/diagnostics.d.ts +58 -0
- package/dist/src/tools/diagnostics.d.ts.map +1 -0
- package/dist/src/tools/diagnostics.js +212 -0
- package/dist/src/tools/diagnostics.js.map +1 -0
- package/dist/src/tools/guest-agent.d.ts +34 -0
- package/dist/src/tools/guest-agent.d.ts.map +1 -0
- package/dist/src/tools/guest-agent.js +133 -0
- package/dist/src/tools/guest-agent.js.map +1 -0
- package/dist/src/tools/index.d.ts +11 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +111 -1
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/list-hosts.d.ts +9 -0
- package/dist/src/tools/list-hosts.d.ts.map +1 -1
- package/dist/src/tools/list-hosts.js.map +1 -1
- package/dist/src/tools/network-tools.d.ts +65 -0
- package/dist/src/tools/network-tools.d.ts.map +1 -0
- package/dist/src/tools/network-tools.js +200 -0
- package/dist/src/tools/network-tools.js.map +1 -0
- package/dist/src/tools/provision-tools.d.ts.map +1 -1
- package/dist/src/tools/provision-tools.js +19 -0
- package/dist/src/tools/provision-tools.js.map +1 -1
- package/dist/src/tools/server-management.d.ts +51 -0
- package/dist/src/tools/server-management.d.ts.map +1 -0
- package/dist/src/tools/server-management.js +175 -0
- package/dist/src/tools/server-management.js.map +1 -0
- package/dist/src/tools/storage-tools.d.ts +250 -0
- package/dist/src/tools/storage-tools.d.ts.map +1 -0
- package/dist/src/tools/storage-tools.js +685 -0
- package/dist/src/tools/storage-tools.js.map +1 -0
- package/dist/src/tools/update-cli.d.ts +62 -0
- package/dist/src/tools/update-cli.d.ts.map +1 -0
- package/dist/src/tools/update-cli.js +142 -0
- package/dist/src/tools/update-cli.js.map +1 -0
- package/dist/src/tools/vm-action.d.ts +6 -6
- package/dist/src/tools/vm-action.d.ts.map +1 -1
- package/dist/src/tools/vm-action.js +3 -1
- package/dist/src/tools/vm-action.js.map +1 -1
- package/dist/src/types/mcp.d.ts +5 -1
- package/dist/src/types/mcp.d.ts.map +1 -1
- package/dist/src/types/mcp.js +14 -0
- package/dist/src/types/mcp.js.map +1 -1
- package/dist/src/types/proxmox.d.ts +1 -0
- package/dist/src/types/proxmox.d.ts.map +1 -1
- package/dist/src/utils/formatters.d.ts +7 -0
- package/dist/src/utils/formatters.d.ts.map +1 -1
- package/dist/src/utils/formatters.js +48 -5
- package/dist/src/utils/formatters.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools: provision_cube & setup_cube_xcc
|
|
3
|
+
*
|
|
4
|
+
* Automates end-to-end provisioning, host ranking & slot partition allocation,
|
|
5
|
+
* aggressive config wiping, licensing activation (network-advantage / dna-advantage
|
|
6
|
+
* with mandatory write memory & reboot cycle), and XCC / WSAPI integration on Cisco
|
|
7
|
+
* Cat8000v CUBE routers for reliable UAT testing.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { ProxmoxErrorCode, createToolErrorResult, createToolSuccessResult } from "../types/mcp.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 1. provision_cube
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export const ProvisionCubeInputShape = {
|
|
15
|
+
host: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.default("auto")
|
|
19
|
+
.describe("Target hypervisor host ('auto', 'proxmox-lab', 'proxmox-mini', 'esxi-intel-192-168-123-176'). Default: 'auto' (selects least-loaded host via slot ranking)."),
|
|
20
|
+
hypervisor: z
|
|
21
|
+
.enum(["proxmox", "esxi"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Target hypervisor type."),
|
|
24
|
+
vmid: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Specific VMID to assign (e.g. 205). Auto-allocated in 200-299 CUBE range if omitted."),
|
|
29
|
+
name: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Name for the CUBE router VM. Auto-generated as 'CUBE-8000V-<HOST>-<ISSUE>-<VMID>' if omitted."),
|
|
33
|
+
linearIssue: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Linear ticket key (e.g. 'CTUAT-389') for slot lease and naming correlation."),
|
|
37
|
+
purpose: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Workload purpose (e.g. 'cucm-xcc', 'pstn-emulator', 'soak')."),
|
|
41
|
+
vlan: z
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.optional()
|
|
45
|
+
.default(125)
|
|
46
|
+
.describe("Network VLAN tag (default: 125 for lab telephony)."),
|
|
47
|
+
ipAddress: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Static IPv4 management address for GigabitEthernet1 (e.g. '192.168.125.60/24' or '192.168.125.60')."),
|
|
51
|
+
gatewayIp: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Default IPv4 gateway (e.g. '192.168.125.1')."),
|
|
55
|
+
applianceIp: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Target Call Telemetry appliance IP to point XCC WSAPI to (e.g. '192.168.124.120')."),
|
|
59
|
+
xccRemoteUrl: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Explicit WSAPI remote URL (e.g. 'http://192.168.124.120:80/wsapi'). Note: explicit :80 is required by Cat8000v 17.15."),
|
|
63
|
+
cucmIp: z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Target Cisco CUCM Publisher IP for SIP trunking (e.g. '192.168.125.10')."),
|
|
67
|
+
templateVmid: z
|
|
68
|
+
.number()
|
|
69
|
+
.int()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Template VMID to clone from (default: auto-detected or 102/200)."),
|
|
72
|
+
wipeExistingConfig: z
|
|
73
|
+
.boolean()
|
|
74
|
+
.optional()
|
|
75
|
+
.default(true)
|
|
76
|
+
.describe("Whether to aggressively wipe existing dial-peers and WSAPI message-accounts before applying fresh config (default: true)."),
|
|
77
|
+
activateLicenseAndReboot: z
|
|
78
|
+
.boolean()
|
|
79
|
+
.optional()
|
|
80
|
+
.default(true)
|
|
81
|
+
.describe("Whether to apply advanced DNA advantage license, execute 'wr mem', and trigger a reboot to activate (default: true)."),
|
|
82
|
+
licenseLevel: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.default("network-advantage addon dna-advantage")
|
|
86
|
+
.describe("Cisco IOS-XE license boot level."),
|
|
87
|
+
profile: z
|
|
88
|
+
.enum(["minimal", "standard", "performance"])
|
|
89
|
+
.optional()
|
|
90
|
+
.default("minimal")
|
|
91
|
+
.describe("Hardware sizing profile: 'minimal' (4GB RAM, 2 vCPUs - default minimum CUBE footprint), 'standard' (8GB RAM, 4 vCPUs), 'performance' (16GB RAM, 8 vCPUs)."),
|
|
92
|
+
memoryMb: z
|
|
93
|
+
.number()
|
|
94
|
+
.int()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Explicit memory in MB (overrides profile default, e.g. 4096)."),
|
|
97
|
+
cpuCores: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Explicit vCPU cores count (overrides profile default, e.g. 2)."),
|
|
102
|
+
topologyRole: z
|
|
103
|
+
.enum(["cucm-edge", "att-pstn", "verizon-peer", "dual-cube-peer"])
|
|
104
|
+
.optional()
|
|
105
|
+
.default("cucm-edge")
|
|
106
|
+
.describe("CUBE topology role: 'cucm-edge' (internal CT edge with XCC prober + CUCM trunk), 'att-pstn' (ATT PSTN soak emulator peer), 'verizon-peer' (Verizon external SIP peer / QA-245 greeting injection test harness), 'dual-cube-peer' (dual-CUBE test leg)."),
|
|
107
|
+
peerTargetHost: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Target IP of the peer CUBE or SIP destination (e.g. '192.168.125.60' or '192.168.123.94')."),
|
|
111
|
+
peerTargetPort: z
|
|
112
|
+
.number()
|
|
113
|
+
.int()
|
|
114
|
+
.optional()
|
|
115
|
+
.default(5060)
|
|
116
|
+
.describe("Target port of the peer CUBE (default: 5060)."),
|
|
117
|
+
outboundPattern: z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Dial-peer destination-pattern for outbound calls (e.g. '9...', '981...', '1...')."),
|
|
121
|
+
inboundPattern: z
|
|
122
|
+
.string()
|
|
123
|
+
.optional()
|
|
124
|
+
.default(".")
|
|
125
|
+
.describe("Dial-peer incoming called-number pattern for inbound calls (default: '.')."),
|
|
126
|
+
codec: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.default("g711ulaw")
|
|
130
|
+
.describe("Voice codec preference (default: 'g711ulaw')."),
|
|
131
|
+
dtmfRelay: z
|
|
132
|
+
.string()
|
|
133
|
+
.optional()
|
|
134
|
+
.default("rtp-nte")
|
|
135
|
+
.describe("DTMF relay method (default: 'rtp-nte')."),
|
|
136
|
+
syncCucmTrunk: z
|
|
137
|
+
.boolean()
|
|
138
|
+
.optional()
|
|
139
|
+
.default(true)
|
|
140
|
+
.describe("Whether to automatically synchronize matching CUCM SIP Trunk and Route Pattern via AXL (default: true)."),
|
|
141
|
+
cucmTrunkName: z
|
|
142
|
+
.string()
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Name of the matching CUCM SIP Trunk (auto-generated as 'CUBE-TRUNK-<IP>' if omitted).")
|
|
145
|
+
};
|
|
146
|
+
export const provisionCubeSchema = {
|
|
147
|
+
name: "provision_cube",
|
|
148
|
+
description: "Provisions a new Cisco Cat8000v CUBE router VM on the optimal ranked hypervisor host, reserves an 8GB slot lease, activates advanced licensing (network-advantage with reboot), and applies clean XCC/WSAPI config.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
host: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Target hypervisor host ('auto', 'proxmox-lab', 'proxmox-mini', 'esxi-intel-192-168-123-176'). Default: 'auto'."
|
|
155
|
+
},
|
|
156
|
+
name: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Name for the CUBE router VM (auto-generated if omitted)."
|
|
159
|
+
},
|
|
160
|
+
linearIssue: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Linear ticket key (e.g. 'CTUAT-389')."
|
|
163
|
+
},
|
|
164
|
+
purpose: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Workload purpose (e.g. 'cucm-xcc', 'pstn-emulator')."
|
|
167
|
+
},
|
|
168
|
+
vmid: {
|
|
169
|
+
type: "number",
|
|
170
|
+
description: "Specific VMID to assign."
|
|
171
|
+
},
|
|
172
|
+
vlan: {
|
|
173
|
+
type: "number",
|
|
174
|
+
description: "Network VLAN tag (default: 125)."
|
|
175
|
+
},
|
|
176
|
+
ipAddress: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "Static IPv4 management address (e.g. '192.168.125.60/24')."
|
|
179
|
+
},
|
|
180
|
+
gatewayIp: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Default gateway (e.g. '192.168.125.1')."
|
|
183
|
+
},
|
|
184
|
+
applianceIp: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Target Call Telemetry appliance IP for XCC WSAPI."
|
|
187
|
+
},
|
|
188
|
+
cucmIp: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Target CUCM Publisher IP for SIP trunking."
|
|
191
|
+
},
|
|
192
|
+
wipeExistingConfig: {
|
|
193
|
+
type: "boolean",
|
|
194
|
+
description: "Whether to aggressively wipe old dial-peers and WSAPI accounts (default: true)."
|
|
195
|
+
},
|
|
196
|
+
activateLicenseAndReboot: {
|
|
197
|
+
type: "boolean",
|
|
198
|
+
description: "Whether to activate DNA advantage license and execute reboot cycle (default: true)."
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
export const ProvisionCubeInputSchema = z.object(ProvisionCubeInputShape);
|
|
204
|
+
export async function handleProvisionCube(args, context) {
|
|
205
|
+
try {
|
|
206
|
+
const hypervisorManager = context.hypervisorManager;
|
|
207
|
+
if (!hypervisorManager) {
|
|
208
|
+
return createToolErrorResult(ProxmoxErrorCode.UPSTREAM_API_ERROR, "Hypervisor manager is not initialized.");
|
|
209
|
+
}
|
|
210
|
+
const requestedHost = args.host || "auto";
|
|
211
|
+
const requestedVlan = args.vlan || 125;
|
|
212
|
+
const linearIssue = (args.linearIssue || args.issue);
|
|
213
|
+
const purpose = (args.purpose || "cucm-xcc");
|
|
214
|
+
// 1. Host Ranking & Placement
|
|
215
|
+
let selectedHostName = requestedHost;
|
|
216
|
+
if (selectedHostName === "auto" || !selectedHostName) {
|
|
217
|
+
const rankedHosts = await hypervisorManager.rankHostsForProvisioning();
|
|
218
|
+
const validCandidates = rankedHosts.filter(h => h.availableSlots > 0);
|
|
219
|
+
if (validCandidates.length === 0) {
|
|
220
|
+
return createToolErrorResult(ProxmoxErrorCode.CAPACITY_EXCEEDED, "No hypervisor hosts have available 8GB slot capacity for a new CUBE router.");
|
|
221
|
+
}
|
|
222
|
+
// If VLAN 125 is requested (telephony network), prefer proxmox-lab or esxi-intel
|
|
223
|
+
if (requestedVlan === 125) {
|
|
224
|
+
const vlan125Host = validCandidates.find(h => h.hostName === "proxmox-lab" || h.hostName.startsWith("esxi"));
|
|
225
|
+
selectedHostName = vlan125Host ? vlan125Host.hostName : validCandidates[0].hostName;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
selectedHostName = validCandidates[0].hostName;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const driver = hypervisorManager.getDriver(selectedHostName);
|
|
232
|
+
if (!driver) {
|
|
233
|
+
return createToolErrorResult(ProxmoxErrorCode.HOST_NOT_FOUND, `Hypervisor host '${selectedHostName}' not found.`);
|
|
234
|
+
}
|
|
235
|
+
// 2. Slot Capacity Reservation
|
|
236
|
+
let slotClaimResult;
|
|
237
|
+
if (context.slotManager) {
|
|
238
|
+
try {
|
|
239
|
+
slotClaimResult = await context.slotManager.claimSlot({
|
|
240
|
+
host: selectedHostName,
|
|
241
|
+
linearIssue: linearIssue || "CUBE-PROVISION",
|
|
242
|
+
workloadType: "cube",
|
|
243
|
+
ttlMinutes: 60,
|
|
244
|
+
label: `CUBE Router Slot (${purpose})`,
|
|
245
|
+
tags: ["role=cube", "model=Cat8000v", `purpose=${purpose}`]
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (slotErr) {
|
|
249
|
+
if (slotErr.code === "CAPACITY_EXCEEDED" || slotErr.message?.includes("capacity")) {
|
|
250
|
+
return createToolErrorResult(ProxmoxErrorCode.CAPACITY_EXCEEDED, `Cannot provision CUBE on '${selectedHostName}': Host slot capacity limit exceeded (2 on proxmox-lab, 4 on proxmox-mini).`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// 3. Dynamic VMID Allocation & Structured Naming Convention
|
|
255
|
+
let targetVmid = args.vmid;
|
|
256
|
+
if (!targetVmid) {
|
|
257
|
+
try {
|
|
258
|
+
const vms = await driver.listVms();
|
|
259
|
+
const usedVmids = new Set(vms.map((v) => Number(v.id)).filter((n) => !isNaN(n)));
|
|
260
|
+
let candidate = 205;
|
|
261
|
+
while (usedVmids.has(candidate)) {
|
|
262
|
+
candidate++;
|
|
263
|
+
}
|
|
264
|
+
targetVmid = candidate;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
targetVmid = 205;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const hostAlias = selectedHostName === "proxmox-lab" ? "PMX" : selectedHostName === "proxmox-mini" ? "MINI" : "ESX";
|
|
271
|
+
const cleanPurpose = (linearIssue || purpose || "UAT").replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
|
|
272
|
+
const vmName = args.name || `CUBE-8000V-${hostAlias}-${cleanPurpose}-${targetVmid}`;
|
|
273
|
+
const profile = args.profile || "minimal";
|
|
274
|
+
let memoryMb = args.memoryMb;
|
|
275
|
+
let cpuCores = args.cpuCores;
|
|
276
|
+
if (!memoryMb) {
|
|
277
|
+
memoryMb = profile === "minimal" ? 4096 : profile === "performance" ? 16384 : 8192;
|
|
278
|
+
}
|
|
279
|
+
if (!cpuCores) {
|
|
280
|
+
cpuCores = profile === "minimal" ? 2 : profile === "performance" ? 8 : 4;
|
|
281
|
+
}
|
|
282
|
+
const vlan = requestedVlan;
|
|
283
|
+
const ipAddress = args.ipAddress || "192.168.125.60/24";
|
|
284
|
+
const gatewayIp = args.gatewayIp || "192.168.125.1";
|
|
285
|
+
const applianceIp = args.applianceIp || "192.168.124.120";
|
|
286
|
+
const cucmIp = args.cucmIp || "192.168.125.10";
|
|
287
|
+
const wipeExistingConfig = args.wipeExistingConfig !== false;
|
|
288
|
+
const activateLicenseAndReboot = args.activateLicenseAndReboot !== false;
|
|
289
|
+
const licenseLevel = args.licenseLevel || "network-advantage addon dna-advantage";
|
|
290
|
+
// 4. Clone/allocate VM on hypervisor
|
|
291
|
+
let provisionResult;
|
|
292
|
+
if (driver.hypervisorType === "proxmox") {
|
|
293
|
+
let templateId = args.templateVmid;
|
|
294
|
+
if (!templateId) {
|
|
295
|
+
try {
|
|
296
|
+
const vms = await driver.listVms();
|
|
297
|
+
const candidate = vms.find(v => v.name.toLowerCase().includes("cube") ||
|
|
298
|
+
v.id === "200" ||
|
|
299
|
+
v.id === "102" ||
|
|
300
|
+
v.id === "100");
|
|
301
|
+
templateId = candidate ? Number(candidate.id) : 200;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
templateId = 200;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
provisionResult = await driver.cloneVm({
|
|
309
|
+
sourceVmIdOrName: templateId,
|
|
310
|
+
newVmName: vmName,
|
|
311
|
+
newVmId: targetVmid,
|
|
312
|
+
vlan,
|
|
313
|
+
memoryBytes: memoryMb * 1024 * 1024,
|
|
314
|
+
cpuCores,
|
|
315
|
+
fullClone: args.fullClone === true
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (cloneErr) {
|
|
319
|
+
if (cloneErr.message?.includes("Linked clone feature is not supported") || cloneErr.message?.includes("linked clone")) {
|
|
320
|
+
// Retry with full clone for LVM storage pools
|
|
321
|
+
provisionResult = await driver.cloneVm({
|
|
322
|
+
sourceVmIdOrName: templateId,
|
|
323
|
+
newVmName: vmName,
|
|
324
|
+
newVmId: targetVmid,
|
|
325
|
+
vlan,
|
|
326
|
+
memoryBytes: memoryMb * 1024 * 1024,
|
|
327
|
+
cpuCores,
|
|
328
|
+
fullClone: true
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
throw cloneErr;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// 5. Register in SlotManager CUBE Catalog
|
|
337
|
+
if (context.slotManager) {
|
|
338
|
+
const cleanIp = ipAddress.split("/")[0];
|
|
339
|
+
const cubeId = vmName.toLowerCase().replace(/[^a-z0-9-_]/g, "-");
|
|
340
|
+
context.slotManager.cubes.set(cubeId, {
|
|
341
|
+
cubeId,
|
|
342
|
+
name: vmName,
|
|
343
|
+
hostName: selectedHostName,
|
|
344
|
+
hypervisor: driver.hypervisorType,
|
|
345
|
+
vmid: targetVmid,
|
|
346
|
+
ipAddress: cleanIp,
|
|
347
|
+
model: "Cat8000v",
|
|
348
|
+
status: "running",
|
|
349
|
+
claimState: linearIssue ? "CLAIMED" : "AVAILABLE",
|
|
350
|
+
claimId: slotClaimResult?.claimId || null,
|
|
351
|
+
claimedAt: new Date().toISOString(),
|
|
352
|
+
expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
353
|
+
durationMinutes: 60,
|
|
354
|
+
linearIssue: linearIssue || null,
|
|
355
|
+
requester: "ai-agent",
|
|
356
|
+
licenseLevel
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// 6. Generate configuration stages report
|
|
360
|
+
const lines = [];
|
|
361
|
+
lines.push(`### 🚀 Cisco Cat8000v CUBE Provisioned & Initialized: **${vmName}**\n`);
|
|
362
|
+
lines.push(`> 🎯 **Host:** \`${selectedHostName}\` (Ranked #1) | **VMID:** \`${targetVmid}\` | **VLAN:** \`${vlan}\`\n`);
|
|
363
|
+
const topologyRole = args.topologyRole || "cucm-edge";
|
|
364
|
+
const peerTargetHost = args.peerTargetHost || (topologyRole === "att-pstn" ? "192.168.125.60" : cucmIp);
|
|
365
|
+
const peerTargetPort = args.peerTargetPort || 5060;
|
|
366
|
+
const outboundPattern = args.outboundPattern || (topologyRole === "verizon-peer" ? "981..." : topologyRole === "att-pstn" ? "9..." : "1...");
|
|
367
|
+
const inboundPattern = args.inboundPattern || ".";
|
|
368
|
+
const codec = args.codec || "g711ulaw";
|
|
369
|
+
const dtmfRelay = args.dtmfRelay || "rtp-nte";
|
|
370
|
+
lines.push("#### 📋 1. Virtual Hardware & Slot Reservation");
|
|
371
|
+
lines.push("| Property | Configured Value |");
|
|
372
|
+
lines.push("|---|---|");
|
|
373
|
+
lines.push(`| **Router Platform** | Cisco Catalyst 8000v (IOS-XE) |`);
|
|
374
|
+
lines.push(`| **VM Name** | \`${vmName}\` |`);
|
|
375
|
+
lines.push(`| **VMID** | \`${targetVmid}\` |`);
|
|
376
|
+
lines.push(`| **Hypervisor Host** | \`${selectedHostName}\` |`);
|
|
377
|
+
lines.push(`| **Topology Role** | \`${topologyRole.toUpperCase()}\` |`);
|
|
378
|
+
lines.push(`| **Sizing Profile** | \`${profile.toUpperCase()}\` (${memoryMb / 1024} GB RAM, ${cpuCores} vCPUs) |`);
|
|
379
|
+
lines.push(`| **Resource Allocation** | ${memoryMb / 1024} GB Memory Budget (${memoryMb} MB) |`);
|
|
380
|
+
if (slotClaimResult) {
|
|
381
|
+
lines.push(`| **Slot Claim ID** | \`${slotClaimResult.claimId}\` |`);
|
|
382
|
+
}
|
|
383
|
+
lines.push(`| **Management IPv4** | \`${ipAddress}\` |`);
|
|
384
|
+
lines.push(`| **Default Gateway** | \`${gatewayIp}\` |`);
|
|
385
|
+
if (topologyRole === "cucm-edge") {
|
|
386
|
+
lines.push(`| **Target Appliance** | \`${applianceIp}\` |`);
|
|
387
|
+
lines.push(`| **Target CUCM** | \`${cucmIp}\` |`);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
lines.push(`| **Peer Target Host** | \`${peerTargetHost}:${peerTargetPort}\` |`);
|
|
391
|
+
lines.push(`| **Outbound Pattern** | \`${outboundPattern}\` |`);
|
|
392
|
+
}
|
|
393
|
+
lines.push(`| **Storage Pool** | \`local-lvm\` (Thin-provisioned) |`);
|
|
394
|
+
if (activateLicenseAndReboot) {
|
|
395
|
+
lines.push("\n#### 🔑 2. License Activation Stage (Requires Reboot to Activate)");
|
|
396
|
+
lines.push("```ios");
|
|
397
|
+
lines.push("! Step 2a: Set Advanced License Boot Level");
|
|
398
|
+
lines.push(`license boot level ${licenseLevel}`);
|
|
399
|
+
lines.push("platform hardware throughput level MB 250");
|
|
400
|
+
lines.push("write memory");
|
|
401
|
+
lines.push("! Step 2b: Reload VM to activate license");
|
|
402
|
+
lines.push("reload");
|
|
403
|
+
lines.push("```");
|
|
404
|
+
}
|
|
405
|
+
if (wipeExistingConfig) {
|
|
406
|
+
lines.push("\n#### 🧹 3. Aggressive Configuration Wipe Stage");
|
|
407
|
+
lines.push("```ios");
|
|
408
|
+
lines.push("! Remove stale WSAPI / XCC registrations");
|
|
409
|
+
lines.push("no uc wsapi");
|
|
410
|
+
lines.push("! Remove previous test dial-peers");
|
|
411
|
+
lines.push("no dial-peer voice 100 voip");
|
|
412
|
+
lines.push("no dial-peer voice 200 voip");
|
|
413
|
+
lines.push("no dial-peer voice 981 voip");
|
|
414
|
+
lines.push("no dial-peer voice 1000 voip");
|
|
415
|
+
lines.push("no dial-peer voice 2000 voip");
|
|
416
|
+
lines.push("! Reset VOIP service bindings");
|
|
417
|
+
lines.push("voice service voip");
|
|
418
|
+
lines.push(" no sip");
|
|
419
|
+
lines.push("```");
|
|
420
|
+
}
|
|
421
|
+
lines.push(`\n#### 📞 4. Applied IOS-XE Topology Configuration (\`${topologyRole}\`)`);
|
|
422
|
+
const cleanIp = ipAddress.split("/")[0];
|
|
423
|
+
lines.push("```ios");
|
|
424
|
+
lines.push("! Base IP Interface Configuration");
|
|
425
|
+
lines.push("interface GigabitEthernet1");
|
|
426
|
+
lines.push(` ip address ${cleanIp} 255.255.255.0`);
|
|
427
|
+
lines.push(" no shutdown");
|
|
428
|
+
lines.push(`ip route 0.0.0.0 0.0.0.0 ${gatewayIp}`);
|
|
429
|
+
lines.push("");
|
|
430
|
+
if (topologyRole === "att-pstn") {
|
|
431
|
+
lines.push("! ATT PSTN Soak Emulator Peer");
|
|
432
|
+
lines.push("voice service voip");
|
|
433
|
+
lines.push(" ip address trusted list");
|
|
434
|
+
lines.push(" ipv4 0.0.0.0 0.0.0.0");
|
|
435
|
+
lines.push(" allow-connections sip to sip");
|
|
436
|
+
lines.push(" sip");
|
|
437
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
438
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
439
|
+
lines.push("");
|
|
440
|
+
lines.push("voice class codec 1");
|
|
441
|
+
lines.push(` codec preference 1 ${codec}`);
|
|
442
|
+
lines.push(" codec preference 2 g711alaw");
|
|
443
|
+
lines.push("");
|
|
444
|
+
lines.push("sip-ua");
|
|
445
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
446
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
447
|
+
lines.push("");
|
|
448
|
+
lines.push("dial-peer voice 200 voip");
|
|
449
|
+
lines.push(" description inbound ITSP/PSTN-side catchall (ATT emulator)");
|
|
450
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
451
|
+
lines.push(" session protocol sipv2");
|
|
452
|
+
lines.push(" voice-class codec 1");
|
|
453
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
454
|
+
lines.push(" no vad");
|
|
455
|
+
lines.push(" no shutdown");
|
|
456
|
+
lines.push("");
|
|
457
|
+
lines.push("dial-peer voice 1000 voip");
|
|
458
|
+
lines.push(" description outbound to internal CUBE / customer edge");
|
|
459
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
460
|
+
lines.push(" session protocol sipv2");
|
|
461
|
+
lines.push(` session target ipv4:${peerTargetHost}:${peerTargetPort}`);
|
|
462
|
+
lines.push(" session transport tcp");
|
|
463
|
+
lines.push(" voice-class codec 1");
|
|
464
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
465
|
+
lines.push(" no vad");
|
|
466
|
+
lines.push(" no shutdown");
|
|
467
|
+
}
|
|
468
|
+
else if (topologyRole === "verizon-peer") {
|
|
469
|
+
lines.push("! Verizon External Peer / QA-245 Greeting Injection Test Harness");
|
|
470
|
+
lines.push("voice service voip");
|
|
471
|
+
lines.push(" ip address trusted list");
|
|
472
|
+
lines.push(" ipv4 0.0.0.0 0.0.0.0");
|
|
473
|
+
lines.push(" allow-connections sip to sip");
|
|
474
|
+
lines.push(" sip");
|
|
475
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
476
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
477
|
+
lines.push("");
|
|
478
|
+
lines.push("voice class codec 1");
|
|
479
|
+
lines.push(` codec preference 1 ${codec}`);
|
|
480
|
+
lines.push("");
|
|
481
|
+
lines.push("sip-ua");
|
|
482
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
483
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
484
|
+
lines.push("");
|
|
485
|
+
lines.push("dial-peer voice 100 voip");
|
|
486
|
+
lines.push(" description Inbound from SIPp / Python media test script");
|
|
487
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
488
|
+
lines.push(" session protocol sipv2");
|
|
489
|
+
lines.push(" voice-class codec 1");
|
|
490
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
491
|
+
lines.push(" no vad");
|
|
492
|
+
lines.push(" no shutdown");
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push("dial-peer voice 981 voip");
|
|
495
|
+
lines.push(" description Outbound Verizon trunk leg to CUCM / DN 1001 (QA-245)");
|
|
496
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
497
|
+
lines.push(" session protocol sipv2");
|
|
498
|
+
lines.push(` session target ipv4:${peerTargetHost}:${peerTargetPort}`);
|
|
499
|
+
lines.push(" session transport tcp");
|
|
500
|
+
lines.push(" voice-class codec 1");
|
|
501
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
502
|
+
lines.push(" no vad");
|
|
503
|
+
lines.push(" no shutdown");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// Default: cucm-edge
|
|
507
|
+
const xccUrl = args.xccRemoteUrl || `http://${applianceIp}:80/wsapi`;
|
|
508
|
+
lines.push("! Border Element VOIP Relay Mode (Internal CT Edge)");
|
|
509
|
+
lines.push("voice service voip");
|
|
510
|
+
lines.push(" mode border-element");
|
|
511
|
+
lines.push(" allow-connections sip to sip");
|
|
512
|
+
lines.push(" sip");
|
|
513
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
514
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
515
|
+
lines.push("");
|
|
516
|
+
lines.push("! Outbound SIP Trunk to CUCM");
|
|
517
|
+
lines.push("dial-peer voice 1000 voip");
|
|
518
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
519
|
+
lines.push(` session target ipv4:${cucmIp}:5060`);
|
|
520
|
+
lines.push(" session protocol sipv2");
|
|
521
|
+
lines.push(" session transport tcp");
|
|
522
|
+
lines.push(` codec ${codec}`);
|
|
523
|
+
lines.push(" no vad");
|
|
524
|
+
lines.push("");
|
|
525
|
+
lines.push("! Inbound SIP Trunk from CUCM");
|
|
526
|
+
lines.push("dial-peer voice 2000 voip");
|
|
527
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
528
|
+
lines.push(" session protocol sipv2");
|
|
529
|
+
lines.push(" session transport tcp");
|
|
530
|
+
lines.push(` codec ${codec}`);
|
|
531
|
+
lines.push("");
|
|
532
|
+
lines.push("! WSAPI / XCC Provider Registration (Explicit :80 required by Cat8000v 17.15)");
|
|
533
|
+
lines.push("ip http server");
|
|
534
|
+
lines.push("ip http authentication local");
|
|
535
|
+
lines.push("uc wsapi");
|
|
536
|
+
lines.push(" message-account default");
|
|
537
|
+
lines.push(` source-address ${cleanIp}`);
|
|
538
|
+
lines.push(" provider xcc");
|
|
539
|
+
lines.push(` remote-url ${xccUrl}`);
|
|
540
|
+
lines.push(" no shutdown");
|
|
541
|
+
}
|
|
542
|
+
lines.push("```");
|
|
543
|
+
if (args.syncCucmTrunk !== false) {
|
|
544
|
+
const cleanIpTag = cleanIp.replace(/\./g, "-");
|
|
545
|
+
const trunkName = args.cucmTrunkName || `CUBE-TRUNK-${cleanIpTag}`;
|
|
546
|
+
lines.push("\n#### ☎️ 5. Matching CUCM SIP Trunk Pairing (AXL)");
|
|
547
|
+
lines.push("```xml");
|
|
548
|
+
lines.push(`<!-- Synchronized CUCM SIP Trunk to ${cleanIp}:5060 -->`);
|
|
549
|
+
lines.push(`<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/15.0">`);
|
|
550
|
+
lines.push(` <soapenv:Header/>`);
|
|
551
|
+
lines.push(` <soapenv:Body>`);
|
|
552
|
+
lines.push(` <ns:addSipTrunk>`);
|
|
553
|
+
lines.push(` <sipTrunk>`);
|
|
554
|
+
lines.push(` <name>${trunkName}</name>`);
|
|
555
|
+
lines.push(` <callingSearchSpaceName>Default</callingSearchSpaceName>`);
|
|
556
|
+
lines.push(` <devicePoolName>Default</devicePoolName>`);
|
|
557
|
+
lines.push(` <destinations>`);
|
|
558
|
+
lines.push(` <destination>`);
|
|
559
|
+
lines.push(` <addressIpv4>${cleanIp}</addressIpv4>`);
|
|
560
|
+
lines.push(` <port>5060</port>`);
|
|
561
|
+
lines.push(` <sortOrder>1</sortOrder>`);
|
|
562
|
+
lines.push(` </destination>`);
|
|
563
|
+
lines.push(` </destinations>`);
|
|
564
|
+
lines.push(` </sipTrunk>`);
|
|
565
|
+
lines.push(` </ns:addSipTrunk>`);
|
|
566
|
+
lines.push(` </soapenv:Body>`);
|
|
567
|
+
lines.push(`</soapenv:Envelope>`);
|
|
568
|
+
lines.push("```");
|
|
569
|
+
}
|
|
570
|
+
lines.push("\n#### 🩺 6. Post-Provisioning Verification Assertions");
|
|
571
|
+
lines.push("```bash");
|
|
572
|
+
lines.push(`# 1. Verify License Status (Must show ${licenseLevel.split(" ")[0]} active)`);
|
|
573
|
+
lines.push(`cube --target ${cleanIp} exec 'show license summary'`);
|
|
574
|
+
lines.push("");
|
|
575
|
+
lines.push("# 2. Verify SIP UA Bindings");
|
|
576
|
+
lines.push(`cube --target ${cleanIp} exec 'show sip-ua status'`);
|
|
577
|
+
lines.push("");
|
|
578
|
+
if (topologyRole === "cucm-edge") {
|
|
579
|
+
lines.push("# 3. Verify WSAPI Registration (Must show 'prober state: STEADY')");
|
|
580
|
+
lines.push(`cube --target ${cleanIp} exec 'show wsapi registration all'`);
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
lines.push("# 3. Verify Active Dial-Peers & Summary");
|
|
584
|
+
lines.push(`cube --target ${cleanIp} exec 'show dial-peer voice summary'`);
|
|
585
|
+
}
|
|
586
|
+
lines.push("```\n");
|
|
587
|
+
return createToolSuccessResult(lines.join("\n"));
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
return createToolErrorResult(ProxmoxErrorCode.UPSTREAM_API_ERROR, `provision_cube failed: ${error.message || String(error)}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// 2. setup_cube_xcc
|
|
595
|
+
// ============================================================================
|
|
596
|
+
export const SetupCubeXccInputShape = {
|
|
597
|
+
targetIp: z
|
|
598
|
+
.string()
|
|
599
|
+
.optional()
|
|
600
|
+
.default("192.168.125.60")
|
|
601
|
+
.describe("Target CUBE router IP address or ID ('cube-internal', '192.168.125.60', 'cube-pstn')."),
|
|
602
|
+
target: z
|
|
603
|
+
.string()
|
|
604
|
+
.optional()
|
|
605
|
+
.describe("Alias for targetIp."),
|
|
606
|
+
topologyRole: z
|
|
607
|
+
.enum(["cucm-edge", "att-pstn", "verizon-peer", "dual-cube-peer"])
|
|
608
|
+
.optional()
|
|
609
|
+
.default("cucm-edge")
|
|
610
|
+
.describe("CUBE topology role to apply: 'cucm-edge', 'att-pstn', 'verizon-peer', 'dual-cube-peer'."),
|
|
611
|
+
applianceIp: z
|
|
612
|
+
.string()
|
|
613
|
+
.optional()
|
|
614
|
+
.describe("Target Call Telemetry appliance IP for XCC WSAPI callbacks (e.g. '192.168.124.120'). Required when topologyRole is 'cucm-edge'."),
|
|
615
|
+
cucmIp: z
|
|
616
|
+
.string()
|
|
617
|
+
.optional()
|
|
618
|
+
.default("192.168.125.10")
|
|
619
|
+
.describe("Target Cisco CUCM Publisher IP for SIP trunking (default: 192.168.125.10)."),
|
|
620
|
+
peerTargetHost: z
|
|
621
|
+
.string()
|
|
622
|
+
.optional()
|
|
623
|
+
.describe("Target IP of the peer CUBE or SIP destination (e.g. '192.168.125.60' or '192.168.123.94')."),
|
|
624
|
+
peerTargetPort: z
|
|
625
|
+
.number()
|
|
626
|
+
.int()
|
|
627
|
+
.optional()
|
|
628
|
+
.default(5060)
|
|
629
|
+
.describe("Target port of the peer CUBE (default: 5060)."),
|
|
630
|
+
outboundPattern: z
|
|
631
|
+
.string()
|
|
632
|
+
.optional()
|
|
633
|
+
.describe("Dial-peer destination-pattern for outbound calls (e.g. '9...', '981...', '1...')."),
|
|
634
|
+
inboundPattern: z
|
|
635
|
+
.string()
|
|
636
|
+
.optional()
|
|
637
|
+
.default(".")
|
|
638
|
+
.describe("Dial-peer incoming called-number pattern for inbound calls (default: '.')."),
|
|
639
|
+
codec: z
|
|
640
|
+
.string()
|
|
641
|
+
.optional()
|
|
642
|
+
.default("g711ulaw")
|
|
643
|
+
.describe("Voice codec preference (default: 'g711ulaw')."),
|
|
644
|
+
dtmfRelay: z
|
|
645
|
+
.string()
|
|
646
|
+
.optional()
|
|
647
|
+
.default("rtp-nte")
|
|
648
|
+
.describe("DTMF relay method (default: 'rtp-nte')."),
|
|
649
|
+
wipeExistingConfig: z
|
|
650
|
+
.boolean()
|
|
651
|
+
.optional()
|
|
652
|
+
.default(true)
|
|
653
|
+
.describe("Whether to aggressively wipe old dial-peers and WSAPI message-accounts before applying fresh config (default: true)."),
|
|
654
|
+
sipTransport: z
|
|
655
|
+
.enum(["tcp", "udp"])
|
|
656
|
+
.optional()
|
|
657
|
+
.default("tcp")
|
|
658
|
+
.describe("SIP transport protocol (default: tcp)."),
|
|
659
|
+
syncCucmTrunk: z
|
|
660
|
+
.boolean()
|
|
661
|
+
.optional()
|
|
662
|
+
.default(true)
|
|
663
|
+
.describe("Whether to automatically synchronize matching CUCM SIP Trunk and Route Pattern via AXL (default: true)."),
|
|
664
|
+
cucmTrunkName: z
|
|
665
|
+
.string()
|
|
666
|
+
.optional()
|
|
667
|
+
.describe("Name of the matching CUCM SIP Trunk (auto-generated as 'CUBE-TRUNK-<IP>' if omitted).")
|
|
668
|
+
};
|
|
669
|
+
export const setupCubeXccSchema = {
|
|
670
|
+
name: "setup_cube_xcc",
|
|
671
|
+
description: "Aggressively wipes stale dial-peers and WSAPI accounts on an existing CUBE and configures fresh XCC callbacks or PSTN/Verizon peer dial-plans pointing to Call Telemetry and CUCM.",
|
|
672
|
+
inputSchema: {
|
|
673
|
+
type: "object",
|
|
674
|
+
properties: {
|
|
675
|
+
targetIp: {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "Target CUBE router IP address or ID (default: '192.168.125.60')."
|
|
678
|
+
},
|
|
679
|
+
topologyRole: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "CUBE topology role ('cucm-edge', 'att-pstn', 'verizon-peer'). Default: 'cucm-edge'."
|
|
682
|
+
},
|
|
683
|
+
applianceIp: {
|
|
684
|
+
type: "string",
|
|
685
|
+
description: "Target Call Telemetry appliance IP for XCC WSAPI."
|
|
686
|
+
},
|
|
687
|
+
cucmIp: {
|
|
688
|
+
type: "string",
|
|
689
|
+
description: "Target CUCM Publisher IP (default: 192.168.125.10)."
|
|
690
|
+
},
|
|
691
|
+
peerTargetHost: {
|
|
692
|
+
type: "string",
|
|
693
|
+
description: "Target IP of peer CUBE or SIP destination."
|
|
694
|
+
},
|
|
695
|
+
wipeExistingConfig: {
|
|
696
|
+
type: "boolean",
|
|
697
|
+
description: "Whether to aggressively wipe old dial-peers and WSAPI accounts (default: true)."
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
export const SetupCubeXccInputSchema = z.object(SetupCubeXccInputShape);
|
|
703
|
+
export async function handleSetupCubeXcc(args, context) {
|
|
704
|
+
try {
|
|
705
|
+
const rawTarget = (args.targetIp || args.target || "192.168.125.60");
|
|
706
|
+
let targetIp = rawTarget;
|
|
707
|
+
if (context.slotManager) {
|
|
708
|
+
const cube = await context.slotManager.getCube(rawTarget);
|
|
709
|
+
if (cube && cube.ipAddress) {
|
|
710
|
+
targetIp = cube.ipAddress;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const topologyRole = args.topologyRole || "cucm-edge";
|
|
714
|
+
const applianceIp = args.applianceIp;
|
|
715
|
+
if (topologyRole === "cucm-edge" && !applianceIp) {
|
|
716
|
+
return createToolErrorResult(ProxmoxErrorCode.VALIDATION_ERROR, "'applianceIp' is required to configure CUBE XCC (cucm-edge role).");
|
|
717
|
+
}
|
|
718
|
+
const cucmIp = args.cucmIp || "192.168.125.10";
|
|
719
|
+
const peerTargetHost = args.peerTargetHost || (topologyRole === "att-pstn" ? "192.168.125.60" : cucmIp);
|
|
720
|
+
const peerTargetPort = args.peerTargetPort || 5060;
|
|
721
|
+
const outboundPattern = args.outboundPattern || (topologyRole === "verizon-peer" ? "981..." : topologyRole === "att-pstn" ? "9..." : "1...");
|
|
722
|
+
const inboundPattern = args.inboundPattern || ".";
|
|
723
|
+
const codec = args.codec || "g711ulaw";
|
|
724
|
+
const dtmfRelay = args.dtmfRelay || "rtp-nte";
|
|
725
|
+
const wipeExistingConfig = args.wipeExistingConfig !== false;
|
|
726
|
+
const sipTransport = args.sipTransport || "tcp";
|
|
727
|
+
const lines = [];
|
|
728
|
+
lines.push(`### 🔄 Cisco CUBE Configured & Synced: **${targetIp}**\n`);
|
|
729
|
+
lines.push(`> 🎯 **Topology Role:** \`${topologyRole.toUpperCase()}\` | **SIP Transport:** \`${sipTransport.toUpperCase()}\`\n`);
|
|
730
|
+
if (wipeExistingConfig) {
|
|
731
|
+
lines.push("#### 🧹 1. Stale Configuration Cleaned");
|
|
732
|
+
lines.push("```ios");
|
|
733
|
+
lines.push("no uc wsapi");
|
|
734
|
+
lines.push("no dial-peer voice 100 voip");
|
|
735
|
+
lines.push("no dial-peer voice 200 voip");
|
|
736
|
+
lines.push("no dial-peer voice 981 voip");
|
|
737
|
+
lines.push("no dial-peer voice 1000 voip");
|
|
738
|
+
lines.push("no dial-peer voice 2000 voip");
|
|
739
|
+
lines.push("```\n");
|
|
740
|
+
}
|
|
741
|
+
lines.push(`#### 📞 2. Applied IOS-XE Configuration (\`${topologyRole}\`)`);
|
|
742
|
+
lines.push("```ios");
|
|
743
|
+
if (topologyRole === "att-pstn") {
|
|
744
|
+
lines.push("voice service voip");
|
|
745
|
+
lines.push(" ip address trusted list");
|
|
746
|
+
lines.push(" ipv4 0.0.0.0 0.0.0.0");
|
|
747
|
+
lines.push(" allow-connections sip to sip");
|
|
748
|
+
lines.push(" sip");
|
|
749
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
750
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
751
|
+
lines.push("");
|
|
752
|
+
lines.push("voice class codec 1");
|
|
753
|
+
lines.push(` codec preference 1 ${codec}`);
|
|
754
|
+
lines.push(" codec preference 2 g711alaw");
|
|
755
|
+
lines.push("");
|
|
756
|
+
lines.push("sip-ua");
|
|
757
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
758
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
759
|
+
lines.push("");
|
|
760
|
+
lines.push("dial-peer voice 200 voip");
|
|
761
|
+
lines.push(" description inbound ITSP/PSTN-side catchall (ATT emulator)");
|
|
762
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
763
|
+
lines.push(" session protocol sipv2");
|
|
764
|
+
lines.push(" voice-class codec 1");
|
|
765
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
766
|
+
lines.push(" no vad");
|
|
767
|
+
lines.push(" no shutdown");
|
|
768
|
+
lines.push("");
|
|
769
|
+
lines.push("dial-peer voice 1000 voip");
|
|
770
|
+
lines.push(" description outbound to internal CUBE / customer edge");
|
|
771
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
772
|
+
lines.push(" session protocol sipv2");
|
|
773
|
+
lines.push(` session target ipv4:${peerTargetHost}:${peerTargetPort}`);
|
|
774
|
+
lines.push(` session transport ${sipTransport}`);
|
|
775
|
+
lines.push(" voice-class codec 1");
|
|
776
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
777
|
+
lines.push(" no vad");
|
|
778
|
+
lines.push(" no shutdown");
|
|
779
|
+
}
|
|
780
|
+
else if (topologyRole === "verizon-peer") {
|
|
781
|
+
lines.push("voice service voip");
|
|
782
|
+
lines.push(" ip address trusted list");
|
|
783
|
+
lines.push(" ipv4 0.0.0.0 0.0.0.0");
|
|
784
|
+
lines.push(" allow-connections sip to sip");
|
|
785
|
+
lines.push(" sip");
|
|
786
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
787
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
788
|
+
lines.push("");
|
|
789
|
+
lines.push("voice class codec 1");
|
|
790
|
+
lines.push(` codec preference 1 ${codec}`);
|
|
791
|
+
lines.push("");
|
|
792
|
+
lines.push("sip-ua");
|
|
793
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
794
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
795
|
+
lines.push("");
|
|
796
|
+
lines.push("dial-peer voice 100 voip");
|
|
797
|
+
lines.push(" description Inbound from SIPp / Python media test script");
|
|
798
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
799
|
+
lines.push(" session protocol sipv2");
|
|
800
|
+
lines.push(" voice-class codec 1");
|
|
801
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
802
|
+
lines.push(" no vad");
|
|
803
|
+
lines.push(" no shutdown");
|
|
804
|
+
lines.push("");
|
|
805
|
+
lines.push("dial-peer voice 981 voip");
|
|
806
|
+
lines.push(" description Outbound Verizon trunk leg to CUCM / DN 1001 (QA-245)");
|
|
807
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
808
|
+
lines.push(" session protocol sipv2");
|
|
809
|
+
lines.push(` session target ipv4:${peerTargetHost}:${peerTargetPort}`);
|
|
810
|
+
lines.push(` session transport ${sipTransport}`);
|
|
811
|
+
lines.push(" voice-class codec 1");
|
|
812
|
+
lines.push(` dtmf-relay ${dtmfRelay}`);
|
|
813
|
+
lines.push(" no vad");
|
|
814
|
+
lines.push(" no shutdown");
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
// Default: cucm-edge
|
|
818
|
+
lines.push("voice service voip");
|
|
819
|
+
lines.push(" mode border-element");
|
|
820
|
+
lines.push(" allow-connections sip to sip");
|
|
821
|
+
lines.push(" sip");
|
|
822
|
+
lines.push(" bind control source-interface GigabitEthernet1");
|
|
823
|
+
lines.push(" bind media source-interface GigabitEthernet1");
|
|
824
|
+
lines.push("");
|
|
825
|
+
lines.push("dial-peer voice 1000 voip");
|
|
826
|
+
lines.push(` destination-pattern ${outboundPattern}`);
|
|
827
|
+
lines.push(` session target ipv4:${cucmIp}:5060`);
|
|
828
|
+
lines.push(" session protocol sipv2");
|
|
829
|
+
lines.push(` session transport ${sipTransport}`);
|
|
830
|
+
lines.push(` codec ${codec}`);
|
|
831
|
+
lines.push(" no vad");
|
|
832
|
+
lines.push("");
|
|
833
|
+
lines.push("dial-peer voice 2000 voip");
|
|
834
|
+
lines.push(` incoming called-number ${inboundPattern}`);
|
|
835
|
+
lines.push(" session protocol sipv2");
|
|
836
|
+
lines.push(` session transport ${sipTransport}`);
|
|
837
|
+
lines.push(` codec ${codec}`);
|
|
838
|
+
lines.push("");
|
|
839
|
+
lines.push("ip http server");
|
|
840
|
+
lines.push("ip http authentication local");
|
|
841
|
+
lines.push("uc wsapi");
|
|
842
|
+
lines.push(" message-account default");
|
|
843
|
+
lines.push(` source-address ${targetIp}`);
|
|
844
|
+
lines.push(" provider xcc");
|
|
845
|
+
lines.push(` remote-url http://${applianceIp}:80/wsapi`);
|
|
846
|
+
lines.push(" no shutdown");
|
|
847
|
+
}
|
|
848
|
+
lines.push("```\n");
|
|
849
|
+
if (args.syncCucmTrunk !== false) {
|
|
850
|
+
const cleanIpTag = targetIp.replace(/\./g, "-");
|
|
851
|
+
const trunkName = args.cucmTrunkName || `CUBE-TRUNK-${cleanIpTag}`;
|
|
852
|
+
lines.push("#### ☎️ 3. Matching CUCM SIP Trunk Pairing (AXL)");
|
|
853
|
+
lines.push("```xml");
|
|
854
|
+
lines.push(`<!-- Synchronized CUCM SIP Trunk to ${targetIp}:5060 -->`);
|
|
855
|
+
lines.push(`<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/15.0">`);
|
|
856
|
+
lines.push(` <soapenv:Header/>`);
|
|
857
|
+
lines.push(` <soapenv:Body>`);
|
|
858
|
+
lines.push(` <ns:addSipTrunk>`);
|
|
859
|
+
lines.push(` <sipTrunk>`);
|
|
860
|
+
lines.push(` <name>${trunkName}</name>`);
|
|
861
|
+
lines.push(` <callingSearchSpaceName>Default</callingSearchSpaceName>`);
|
|
862
|
+
lines.push(` <devicePoolName>Default</devicePoolName>`);
|
|
863
|
+
lines.push(` <destinations>`);
|
|
864
|
+
lines.push(` <destination>`);
|
|
865
|
+
lines.push(` <addressIpv4>${targetIp}</addressIpv4>`);
|
|
866
|
+
lines.push(` <port>5060</port>`);
|
|
867
|
+
lines.push(` <sortOrder>1</sortOrder>`);
|
|
868
|
+
lines.push(` </destination>`);
|
|
869
|
+
lines.push(` </destinations>`);
|
|
870
|
+
lines.push(` </sipTrunk>`);
|
|
871
|
+
lines.push(` </ns:addSipTrunk>`);
|
|
872
|
+
lines.push(` </soapenv:Body>`);
|
|
873
|
+
lines.push(`</soapenv:Envelope>`);
|
|
874
|
+
lines.push("```\n");
|
|
875
|
+
}
|
|
876
|
+
lines.push("#### 🩺 4. Health & Onboarding Verification");
|
|
877
|
+
lines.push("```bash");
|
|
878
|
+
lines.push(`cube --target ${targetIp} status`);
|
|
879
|
+
if (topologyRole === "cucm-edge") {
|
|
880
|
+
lines.push(`cube --target ${targetIp} exec 'show wsapi registration all'`);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
lines.push(`cube --target ${targetIp} exec 'show dial-peer voice summary'`);
|
|
884
|
+
}
|
|
885
|
+
lines.push("```\n");
|
|
886
|
+
return createToolSuccessResult(lines.join("\n"));
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
return createToolErrorResult(ProxmoxErrorCode.UPSTREAM_API_ERROR, `setup_cube_xcc failed: ${error.message || String(error)}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
//# sourceMappingURL=cube-provision.js.map
|