├── LICENSE
├── README.md
├── examples.txt
├── icinga1-setup
└── vi_setup.py
├── icingaexchange.yml
└── pyvinga.py
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 lgeeklee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pyvinga
2 | =======
3 |
4 | This plug-in allows you to monitor your VMware vSphere environment from Icinga. It allows you to connect to individual
5 | ESXi hosts or centralised vCenter servers and uses the Python pyVmomi plugin for this connection.
6 |
7 | Please see the GitHub site Wiki for further details on features, examples and setup instructions.
8 |
9 | Additional information can be found at http://www.geeklee.co.uk/monitor-vsphere-with-python-and-icinga/
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples.txt:
--------------------------------------------------------------------------------
1 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n host -e vmesxi01.lab.local -r cpu.usage -w 5 -c 10
2 | OK - CPU Usage is 2.1% | 'CPU Usage'=2.1%;5.0;10.0;0;100
3 |
4 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n host -e vmesxi01.lab.local -r mem.usage -w 5 -c 10
5 | CRITICAL - Memory Usage is 58.3% | 'Memory Usage'=58.3%;5.0;10.0
6 |
7 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n host -e vmesxi01.lab.local -r core
8 | VMware Virtual Platform, 2 x Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz CPU(s) (2 Cores, 2 Logical), 4 GB Memory
9 |
10 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r core
11 | Test Virtual Machine, Microsoft Windows Server 2008 R2 (64-bit), 1 vCPU(s), 1.0 GB Memory
12 |
13 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r cpu.usage -w 5 -c 10
14 | OK - CPU Usage is 1.1% | 'CPU Usage'=1.1%;5.0;10.0;0;100
15 |
16 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r cpu.ready -w 5 -c 10
17 | OK - CPU Ready is 0.1% | 'CPU Ready'=0.1%;5.0;10.0;0;100
18 |
19 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r mem.active -w 80 -c 90
20 | OK - Memory Active is 81.9MB | 'Memory Active'=81.9MB;819.2;921.6;0;1024
21 |
22 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r mem.balloon -w 40 -c 60
23 | OK - Memory Balloon is 0.0MB | 'Memory Balloon'=0.0MB;409.6;614.4;0;1024
24 |
25 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r mem.shared -w 40 -c 60
26 | OK - Memory Shared is 3.8MB | 'Memory Shared'=3.8MB;409.6;614.4;0;1024
27 |
28 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r datastore.io -w 50 -c 100
29 | OK - Datastore IOPS is 0.0IOPS | 'Datastore IOPS'=0.0IOPS;50.0;100.0;0;5000
30 |
31 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r datastore.latency -w 10 -c 20
32 | OK - Datastore Latency is 0.0ms | 'Datastore Latency'=0.0ms;10.0;20.0;0;100
33 |
34 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n vm -e VMTEST01 -r network.usage -w 1 -c 5
35 | OK - Network Usage is 0.0Mbps | 'Network Usage'=0.0Mbps;1.0;5.0;0;1000
36 |
37 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n datastore -e OF-iSCSI1 -r status
38 | OK - Datastore Status is green (Type: VMFS)
39 |
40 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n datastore -e OF-iSCSI1 -r space -w 80 -c 90
41 | OK - Datastore Used Space is 46.5% (Used 33.9 GB of 73.0 GB) | 'Datastore Used Space'=46.5%;80.0;90.0;0;100
42 |
43 | ++ /opt/pyvinga/pyvinga.py -s vcenterhostname -u svc-pyvinga -p xyz123 -n cluster -e HLCLUSTER -r status
44 | WARNING - Cluster Status is yellow
45 |
46 |
--------------------------------------------------------------------------------
/icinga1-setup/vi_setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | Python program will import VMs and Datastores from an ESXi host or vCenter instance
5 | and create an Icinga hierarchy.
6 | """
7 |
8 | from __future__ import print_function
9 | import argparse
10 | import atexit
11 | import getpass
12 |
13 | from pyVim.connect import SmartConnect, Disconnect
14 | from pyVmomi import vmodl, vim
15 |
16 |
17 | # The path and name of the file where you want the custom command(s) to be stored
18 | vi_var_file = '/etc/icinga/objects/vi_commands.cfg'
19 | # Domain name of your ESXi hosts - only used when querying individual ESXi hosts
20 | domain_name = '.homelab.local'
21 |
22 |
23 | def GetArgs():
24 | """
25 | Supports the command-line arguments listed below.
26 | """
27 | parser = argparse.ArgumentParser(description='Process args for retrieving all the Virtual Machines')
28 | parser.add_argument('-e', '--entity', required=True, action='store', help='Entity to setup (vCenter or ESXi host)')
29 | parser.add_argument('-o', '--port', type=int, default=443, action='store', help='Port to connect on')
30 | parser.add_argument('-u', '--user', required=True, action='store', help='User name to use when connecting to host')
31 | parser.add_argument('-p', '--password', required=False, action='store',
32 | help='Password to use when connecting to host')
33 | args = parser.parse_args()
34 | return args
35 |
36 |
37 | def get_properties(content, viewType, props, specType):
38 | # Build a view and get basic properties for all Virtual Machines
39 | """
40 | Obtains a list of specific properties for a particular Managed Object Reference data object.
41 |
42 | :param content: ServiceInstance Managed Object
43 | :param viewType: Type of Managed Object Reference that should populate the View
44 | :param props: A list of properties that should be retrieved for the entity
45 | :param specType: Type of Managed Object Reference that should be used for the Property Specification
46 | :return:
47 | """
48 | # Get the View based on the viewType
49 | objView = content.viewManager.CreateContainerView(content.rootFolder, viewType, True)
50 | # Build the Filter Specification
51 | tSpec = vim.PropertyCollector.TraversalSpec(name='tSpecName', path='view', skip=False, type=vim.view.ContainerView)
52 | pSpec = vim.PropertyCollector.PropertySpec(all=False, pathSet=props, type=specType)
53 | oSpec = vim.PropertyCollector.ObjectSpec(obj=objView, selectSet=[tSpec], skip=False)
54 | pfSpec = vim.PropertyCollector.FilterSpec(objectSet=[oSpec], propSet=[pSpec], reportMissingObjectsInResults=False)
55 | retOptions = vim.PropertyCollector.RetrieveOptions()
56 | # Retrieve the properties and look for a token coming back with each RetrievePropertiesEx call
57 | # If the token is present it indicates there are more items to be returned.
58 | totalProps = []
59 | retProps = content.propertyCollector.RetrievePropertiesEx(specSet=[pfSpec], options=retOptions)
60 | totalProps += retProps.objects
61 | while retProps.token:
62 | retProps = content.propertyCollector.ContinueRetrievePropertiesEx(token=retProps.token)
63 | totalProps += retProps.objects
64 | objView.Destroy()
65 | # Turn the output in totalProps into a usable dictionary of values
66 | gpOutput = []
67 | for eachProp in totalProps:
68 | propDic = {}
69 | for prop in eachProp.propSet:
70 | propDic[prop.name] = prop.val
71 | propDic['moref'] = eachProp.obj
72 | gpOutput.append(propDic)
73 | return gpOutput
74 |
75 |
76 | def create_commands():
77 | """
78 | Create the file that will store the command definition to for check_pyvi.
79 |
80 | Ensure $USER3$ and $USER4$ are configured. If multiple sets of credentials are stored then
81 | multiple commands would need to be entered into this file.
82 | """
83 | f = open(vi_var_file, 'w')
84 | f.write('#\'check_pyvi\' command definition\n')
85 | f.write('define command {\n')
86 | f.write('\tcommand_name\tcheck_pyvi\n')
87 | f.write(
88 | '\tcommand_line\t/opt/pyvinga/pyvinga.py -s $ARG1$ -u $USER3$ -p $USER4$ -n $ARG2$ -e \'$HOSTNAME$\' -r $ARG3$ -w $ARG4$ -c $ARG5$\n')
89 | f.write('\t}\n\n')
90 | f.close()
91 |
92 |
93 | def create_esxi_config(entity, vmProps, dsProps):
94 | """
95 | Create the the configuration for an ESXi host
96 |
97 | :param entity: The ESXi host passed on the command line
98 | :param vmProps: The Virtual Machine hierarchy details for this ESXi host
99 | :param dsProps: The Datastore hierarchy details for this ESXi host
100 | """
101 | norm_entity = entity.split('.')[0]
102 | h_description = 'Virtual Machines'
103 | hostgroup_type = 'virtual-machines'
104 |
105 | create_esxi_host(entity)
106 |
107 | hostgroup_name = create_esxi_hostgroup(hostgroup_type, entity, norm_entity, h_description)
108 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'CPU Ready', 'vm', 'cpu.ready', 5, 10)
109 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Core Information', 'vm', 'core', 0, 0)
110 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'CPU Usage', 'vm', 'cpu.usage', 50, 90)
111 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Memory Active', 'vm', 'mem.active', 80, 90)
112 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Memory Shared', 'vm', 'mem.shared', 98, 99)
113 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Memory Balloon', 'vm', 'mem.balloon', 50, 75)
114 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Datastore IO', 'vm', 'datastore.io', 250, 500)
115 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Datastore Latency', 'vm', 'datastore.latency', 10, 20)
116 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Network Usage', 'vm', 'network.usage', 10, 100)
117 | for vm in vmProps:
118 | create_esxi_vm(hostgroup_type, hostgroup_name, entity, vm['name'], 0, 0)
119 |
120 | h_description = 'Datastores'
121 | hostgroup_type = 'datastores'
122 |
123 | hostgroup_name = create_esxi_hostgroup(hostgroup_type, entity, norm_entity, h_description)
124 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Datastore Space', 'datastore', 'space', 50, 60)
125 | for ds in dsProps:
126 | create_esxi_ds(hostgroup_type, hostgroup_name, entity, ds['name'], 0, 0)
127 |
128 | h_description = 'Hosts'
129 | hostgroup_type = 'hosts'
130 | create_esxi_service(hostgroup_type, entity, norm_entity, 'generic-service', 'Core Information', 'host', 'core',
131 | 0, 0, group=False)
132 |
133 |
134 | def create_esxi_hostgroup(hostgroup_type, entity, norm_entity, h_description):
135 | """
136 | Create the ESXi Host Group config
137 |
138 | :param hostgroup_type: Hyphen separated friendly name for host group type
139 | :param entity: The ESXi host passed on the command line
140 | :param norm_entity: The ESXi host name passed from command line with the FQDN suffix
141 | :param h_description: Friendly description for host group type
142 | """
143 | vi_entity_file = '/etc/icinga/objects/vi_' + norm_entity + '_config.cfg'
144 | hostgroup_name = norm_entity + '-' + hostgroup_type
145 |
146 | f = open(vi_entity_file, 'a')
147 | f.write('#' + h_description + ' in hostgroup for this entity\n')
148 | f.write('#@' + entity + hostgroup_type + '\n')
149 | f.write('define hostgroup {\n')
150 | f.write('\thostgroup_name\t\t' + hostgroup_name + '\n')
151 | f.write('\talias\t\t\t' + norm_entity + ' ' + h_description + '\n')
152 | f.write('\t}\n\n')
153 | f.close()
154 |
155 | return hostgroup_name
156 |
157 |
158 | def create_esxi_service(hostgroup_type, entity, norm_entity, service_template, s_description, counter_type, counter, warning, critical, group=True):
159 | """
160 | Generates the service definitions for VMs, Host and Datastore on an ESXi host
161 |
162 | :param hostgroup_type: Hyphen separated friendly name for host group type
163 | :param entity: The ESXi host passed on the command line
164 | :param norm_entity: The ESXi host name passed from command line with the FQDN suffix
165 | :param service_template:
166 | :param s_description: Friendly description for the service
167 | :param counter_type: Type of counter being supplied (e.g. vm, host, datastore)
168 | :param counter: Friendly name of the counter
169 | :param warning: The warning value for the counter supplied by the command definition
170 | :param critical: The critical value for the counter supplied by the command definition
171 | :param group: Whether the service should be assigned to a host group
172 | """
173 | vi_entity_file = '/etc/icinga/objects/vi_' + norm_entity + '_config.cfg'
174 | hostgroup_name = norm_entity + '-' + hostgroup_type
175 |
176 | f = open(vi_entity_file, 'a')
177 | f.write('#Service ' + s_description + ' for Virtual Machines\n')
178 | f.write('define service {\n')
179 | f.write('\tuse\t\t\t + service_template + \n')
180 | if group:
181 | f.write('\thostgroup_name\t\t' + hostgroup_name + '\n')
182 | else:
183 | f.write('\thost_name\t\t' + norm_entity + '\n')
184 | f.write('\tservice_description\t' + s_description + '\n')
185 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!' + counter_type + '!' + counter + '!' + str(warning) + '!' + str(critical) + '\n')
186 | f.write('\t}\n\n')
187 | f.close()
188 |
189 |
190 | def create_esxi_host(entity):
191 | """
192 | Generates the ESXi host definition for a stand alone ESXi host connection
193 |
194 | :param entity: The ESXi host passed on the command line
195 | """
196 | norm_entity = entity.split('.')[0]
197 | vi_entity_file = '/etc/icinga/objects/vi_' + norm_entity + '_hosts.cfg'
198 |
199 | f = open(vi_entity_file, 'a')
200 | f.write('#Host ' + norm_entity + '\n')
201 | f.write('define host {\n')
202 | f.write('\tuse\t\t\tgeneric-host\n')
203 | f.write('\thost_name\t\t' + entity + '\n')
204 | f.write('\talias\t\t\t' + norm_entity + '\n')
205 | f.write('\taddress\t\t\t' + entity + '\n')
206 | f.write('\t}\n\n')
207 | f.close()
208 |
209 |
210 | def create_esxi_vm(hostgroup_type, hostgroup_name, entity, host_name, warning, critical):
211 | """
212 | Generates the individual Virtual Machine host definitions for a stand alone ESXi host connection
213 |
214 | :param hostgroup_type: Hyphen separated friendly name for host group type
215 | :param hostgroup_name: Name of the ESXi host group generated previously
216 | :param entity: The ESXi host passed on the command line
217 | :param host_name: The name of the Virtual Machine
218 | :param warning: The warning value for the counter supplied by the command definition
219 | :param critical: The critical value for the counter supplied by the command definition
220 | """
221 | norm_entity = entity.split('.')[0]
222 | vi_entity_file = '/etc/icinga/objects/vi_' + norm_entity + '_hosts.cfg'
223 |
224 | f = open(vi_entity_file, 'a')
225 | f.write('#Stand Alone Host ' + norm_entity + '\n')
226 | f.write('define host {\n')
227 | f.write('\tuse\t\t\tgeneric-host\n')
228 | f.write('\thost_name\t\t' + host_name + '\n')
229 | f.write('\talias\t\t\t' + host_name + '\n')
230 | f.write('\taddress\t\t\t' + host_name + domain_name + '\n')
231 | f.write('\tparents\t\t\t' + norm_entity + '\n')
232 | f.write('\thostgroups\t\t' + hostgroup_name + '\n')
233 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!vm!status!' + str(warning) + '!' + str(critical) + '!' + '\n')
234 | f.write('\t}\n\n')
235 | f.close()
236 |
237 |
238 | def create_esxi_ds(hostgroup_type, hostgroup_name, entity, host_name, warning, critical):
239 | """
240 | Generates the individual Datastore host definitions for a stand alone ESXi host connection
241 |
242 | :param hostgroup_type: Hyphen separated friendly name for host group type
243 | :param hostgroup_name: Name of the ESXi host group generated previously
244 | :param entity: The ESXi host passed on the command line
245 | :param host_name: The name of the Datastore
246 | :param warning: The warning value for the counter supplied by the command definition
247 | :param critical: The critical value for the counter supplied by the command definition
248 | """
249 | norm_entity = entity.split('.')[0]
250 | vi_entity_file = '/etc/icinga/objects/vi_' + norm_entity + '_hosts.cfg'
251 |
252 | f = open(vi_entity_file, 'a')
253 | f.write('#Host ' + norm_entity + '\n')
254 | f.write('define host {\n')
255 | f.write('\tuse\t\t\tgeneric-host\n')
256 | f.write('\thost_name\t\t' + host_name + '\n')
257 | f.write('\talias\t\t\t' + host_name + ' Datastore\n')
258 | f.write('\tparents\t\t\t' + norm_entity + '\n')
259 | f.write('\thostgroups\t\t' + hostgroup_name + '\n')
260 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!datastore!status!' + str(warning) + '!' + str(critical) + '!' + '\n')
261 | f.write('\t}\n\n')
262 | f.close()
263 |
264 |
265 | def create_vcenter_config(entity, vm_props, dc_list, dc_sahost_list, dc_cl_list, cl_host_list, ds_table):
266 | """
267 | Create the configuration for a vCenter instance
268 |
269 | :param entity: The vCenter instance passed on the command line
270 | :param vm_props: The Virtual Machine hierarchy details for this ESXi host
271 | :param dc_list: The unique list of vCenter Datacenters
272 | :param dc_sahost_list: A list of each vCenter Datacenter and its stand alone ESXi hosts
273 | :param dc_cl_list: A list of each vCenter Datacenter and its Clusters
274 | :param cl_host_list: A list of each vCenter Cluster and its ESXi hosts
275 | :param ds_table: The Datastore hierarchy details for this ESXi host
276 | """
277 | # Create variables to hold the unique list of hostgroups as they're created
278 | host_hglist = []
279 | ds_hglist = []
280 | cl_hglist = []
281 | vm_hglist = []
282 |
283 | # Check through each vCenter Datacenter and create the relevant configuration
284 | for dc in dc_list:
285 | if dc_sahost_list:
286 | # Create Datacenter - stand alone ESXi host groups
287 | hostgroup_name = str(dc).lower() + '-hosts'
288 | hostgroup_type = 'Datacenter Hosts'
289 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type)
290 | host_hglist.append(hostgroup_name)
291 | for sahost in dc_sahost_list:
292 | if dc == sahost['dcname']:
293 | # Create stand alone alone ESXi host objects
294 | host_type = 'sahost'
295 | create_vc_host(dc, entity, sahost['hostname'], hostgroup_name, host_type, 0, 0)
296 | # Create stand alone ESXi host = Virtual Machine host groups
297 | hostgroup_name = str(dc).lower() + '-' + str(sahost['hostname']).split('.')[0] + '-vms'
298 | hostgroup_type = 'Host VMs'
299 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type, str(sahost['hostname']).split('.')[0])
300 | vm_hglist.append(hostgroup_name)
301 | #Create Datastore hosts
302 | if ds_table:
303 | # Create Datacenter - Datastore host groups
304 | hostgroup_name = str(dc).lower() + '-datastores'
305 | hostgroup_type = 'Datacenter Datastores'
306 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type)
307 | ds_hglist.append(hostgroup_name)
308 | for ds in ds_table:
309 | if dc == ds['dcname']:
310 | # Create Datacenter Datastore host objects
311 | host_type = 'datastore'
312 | create_vc_host(dc, entity, ds['dsname'], hostgroup_name, host_type, 0, 0)
313 | if dc_cl_list:
314 | for dc_cl in dc_cl_list:
315 | if dc == dc_cl['dcname']:
316 | #Create Datacenter - Cluster host groups
317 | hostgroup_name = str(dc).lower() + '-clusters '
318 | hostgroup_type = 'Datacenter Clusters'
319 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type)
320 | # Create Cluster ESXi host objects
321 | host_type = 'cluster'
322 | create_vc_host(dc, entity, str(dc_cl['clustername']).lower(), hostgroup_name, host_type, 0, 0, dc_cl['clustername'])
323 | cl_hglist.append(hostgroup_name)
324 | # Create Cluster - Virtual MAchines host groups
325 | hostgroup_name = str(dc).lower() + '-' + str(dc_cl['clustername']).lower() + '-vms '
326 | hostgroup_type = 'Cluster VMs'
327 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type, dc_cl['clustername'])
328 | vm_hglist.append(hostgroup_name)
329 | # Create Cluster - ESXi host groups
330 | hostgroup_name = str(dc).lower() + '-' + str(dc_cl['clustername']).lower() + '-hosts '
331 | hostgroup_type = 'Cluster Hosts'
332 | create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type, dc_cl['clustername'])
333 | host_hglist.append(hostgroup_name)
334 | for cl_host in cl_host_list:
335 | if dc_cl['clustername'] == cl_host['clustername']:
336 | # Create Cluster ESXi host objects
337 | host_type = 'clhost'
338 | create_vc_host(dc, entity, cl_host['hostname'], hostgroup_name, host_type, 0, 0, dc_cl['clustername'])
339 |
340 | for vm in vm_props:
341 | # Create Virtual Machine host objects
342 | host_type = 'vm'
343 | if vm['clustername'] == False:
344 | vm_parent = vm['hostname']
345 | else:
346 | vm_parent = vm['clustername']
347 | create_vc_host(vm['dcname'], entity, vm['name'], vm['hostgroup_name'], host_type, 0, 0, vm_parent)
348 |
349 | #Create Virtual Machine services
350 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'CPU Ready', 'vm', 'cpu.ready', 5, 10)
351 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Core Information', 'vm', 'core', 0, 0)
352 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'CPU Usage', 'vm', 'cpu.usage', 50, 90)
353 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Memory Active', 'vm', 'mem.active', 80, 90)
354 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Memory Shared', 'vm', 'mem.shared', 98, 99)
355 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Memory Balloon', 'vm', 'mem.balloon', 50, 60)
356 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Datastore IO', 'vm', 'datastore.io', 250, 500)
357 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Datastore Latency', 'vm', 'datastore.latency', 10, 20)
358 | create_vc_service(entity, ','.join(vm_hglist), 'generic-service', 'Network Usage', 'vm', 'network.usage', 10, 100)
359 | #Create Datastore services
360 | create_vc_service(entity, ','.join(ds_hglist), 'generic-service', 'Datastore Space', 'datastore', 'space', 75, 85)
361 | #Create Host services
362 | create_vc_service(entity, ','.join(host_hglist), 'generic-service', 'Core Information', 'host', 'core', 0, 0)
363 |
364 |
365 | def create_vc_hostgroup(dc, entity, hostgroup_name, hostgroup_type, cl_name=''):
366 | """
367 | Add a hostgroup object to the file named after the vCenter Datacenter
368 |
369 | :param dc: The vCenter Datacenter name
370 | :param entity: The vCenter instance passed on the command line
371 | :param hostgroup_name: The name of the hostgroup
372 | :param hostgroup_type: A descriptive name for the hostgroup
373 | :param cl_name: Optional, but allows a Cluster name to be supplied to the function
374 | """
375 | vi_entity_file = '/etc/icinga/objects/vc_' + dc + '_config.cfg'
376 |
377 | f = open(vi_entity_file, 'a')
378 | f.write('#' + hostgroup_type + ' in hostgroup for ' + entity + '\n')
379 | f.write('define hostgroup {\n')
380 | f.write('\thostgroup_name\t\t' + hostgroup_name + '\n')
381 | f.write('\talias\t\t\t' + dc + ' ' + cl_name + ' ' + hostgroup_type + '\n')
382 | f.write('\t}\n\n')
383 | f.close()
384 |
385 |
386 | def create_vc_host(dc, entity, host_name, hostgroup_name, host_type, warning, critical, cl_name=''):
387 | """
388 | Add a host object to the file named after the vCenter Datacenter
389 |
390 | :param dc: The vCenter Datacenter name
391 | :param entity: The vCenter instance passed on the command line
392 | :param host_name: The name of the actual host object to be monitored
393 | :param hostgroup_name: The name of the hostgroup
394 | :param host_type: A host type that helps with determining which lines to write to the .cfg file.
395 | This will typically be cluster, datastore, clhost, sahost, vm
396 | :param cl_name: Optional, but allows a Cluster name to be supplied to the function
397 | :param warning: The warning value for the counter supplied by the command definition
398 | :param critical: The critical value for the counter supplied by the command definition
399 | """
400 | vi_entity_file = '/etc/icinga/objects/vc_' + dc + '_hosts.cfg'
401 | norm_host = host_name.split('.')[0]
402 |
403 | f = open(vi_entity_file, 'a')
404 | f.write('define host {\n')
405 | f.write('\tuse\t\t\tgeneric-host\n')
406 | if host_type == 'cluster':
407 | f.write('\thost_name\t\t' + cl_name + '\n')
408 | elif host_type == 'clhost' or host_type == 'sahost':
409 | f.write('\thost_name\t\t' + host_name + '\n')
410 | else:
411 | f.write('\thost_name\t\t' + norm_host + '\n')
412 | f.write('\talias\t\t\t' + norm_host + ' ' + host_type + '\n')
413 | if host_type != 'cluster' and host_type != 'datastore':
414 | f.write('\taddress\t\t\t' + host_name + '\n')
415 | if host_type == 'clhost' or host_type == 'vm':
416 | f.write('\tparents\t\t\t' + cl_name + '\n')
417 | f.write('\thostgroups\t\t\t' + hostgroup_name + '\n')
418 | if host_type == 'datastore':
419 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!datastore!status!' + str(warning) + '!' + str(critical) + '\n')
420 | elif host_type == 'cluster':
421 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!cluster!status!' + str(warning) + '!' + str(critical) + '\n')
422 | elif host_type == 'vm':
423 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!vm!status!' + str(warning) + '!' + str(critical) + '\n')
424 | f.write('\t}\n\n')
425 | f.close()
426 |
427 |
428 | def create_vc_service(entity, hostgroup_name, service_template, s_description, counter_type, counter, warning, critical):
429 | """
430 | Add a service object to the file named after the vCenter Datacenter
431 |
432 | :param entity: The vCenter instance passed on the command line
433 | :param hostgroup_name: One or more hostgroups supplied as a comma separated string
434 | :param service_template: The name of a template to use in the service
435 | :param s_description: A descriptive name for the service
436 | :param counter_type: The counter type - can be vm, datastore, host or cluster
437 | :param counter: The counter name, this should match the functions in pyvinga.py
438 | :param warning: The warning value for the counter supplied by the command definition
439 | :param critical: The critical value for the counter supplied by the command definition
440 | """
441 | vi_services_file = '/etc/icinga/objects/vc_services_config.cfg'
442 |
443 | f = open(vi_services_file, 'a')
444 | f.write('#Service ' + s_description + ' for ' + counter_type + '\n')
445 | f.write('define service {\n')
446 | f.write('\tuse\t\t\t' + service_template + '\n')
447 | f.write('\thostgroup_name\t\t' + hostgroup_name + '\n')
448 | f.write('\tservice_description\t' + s_description + '\n')
449 | f.write('\tcheck_command\t\tcheck_pyvi!' + entity + '!' + counter_type + '!' + counter + '!' + str(warning) + '!' + str(critical) + '\n')
450 | f.write('\t}\n\n')
451 | f.close()
452 |
453 |
454 | def get_hierarchy(content):
455 | """
456 | Build the hierarchy of Clusters, Stand ALone Hosts, CLuster Hosts, DataCenters and Virtual Machines
457 |
458 | :param content: ServiceInstance Managed Object
459 | """
460 | vm_props = get_properties(content, [vim.VirtualMachine], ['name', 'runtime.host'], vim.VirtualMachine)
461 | host_props = get_properties(content, [vim.HostSystem], ['name', 'parent'], vim.HostSystem)
462 | # Get the Datacenter to Stand Alone Host list
463 | dc_sahost_list = []
464 | # Get the Datacenter to Cluster list
465 | dc_cl_list = []
466 | # Get the Cluster to Host list
467 | cl_host_list = []
468 | for host in host_props:
469 | if str(host['parent']).startswith('\'vim.Compute'):
470 | host['clustername'] = False
471 | host['dcname'] = host['parent'].parent.parent.name
472 | sahost_list = {}
473 | sahost_list['hostname'] = host['name']
474 | sahost_list['dcname'] = host['dcname']
475 | if sahost_list in dc_sahost_list:
476 | pass
477 | else:
478 | dc_sahost_list.append(sahost_list)
479 | elif str(host['parent']).startswith('\'vim.Cluster'):
480 | host['clustername'] = host['parent'].name
481 | host['dcname'] = host['parent'].parent.parent.name
482 | cl_list = {}
483 | cl_list['clustername'] = host['parent'].name
484 | cl_list['dcname'] = host['dcname']
485 | if cl_list not in dc_cl_list:
486 | dc_cl_list.append(cl_list)
487 | clh_list = {}
488 | clh_list['clustername'] = host['parent'].name
489 | clh_list['hostname'] = host['name']
490 | if clh_list not in cl_host_list:
491 | cl_host_list.append(clh_list)
492 |
493 | for vm in vm_props:
494 | for host in host_props:
495 | if vm['runtime.host'] == host['moref']:
496 | vm['hostname'] = host['name']
497 | vm['clustername'] = host['clustername']
498 | vm['dcname'] = host['dcname']
499 | if host['clustername'] == False:
500 | vm['hostgroup_name'] = str(host['dcname']).lower() + '-' + str(host['name']).split('.')[0].lower() + '-vms'
501 | else:
502 | vm['hostgroup_name'] = str(host['dcname']).lower() + '-' + str(host['clustername']).lower() + '-vms'
503 | del vm['runtime.host']
504 |
505 | #Get unique DC list
506 | dc_list = []
507 | for vm in vm_props:
508 | dc_list.append(vm['dcname'])
509 | dc_list = set(dc_list)
510 |
511 |
512 |
513 | return (vm_props, dc_list, dc_sahost_list, dc_cl_list, cl_host_list)
514 |
515 |
516 | def get_datastore_hierarchy(content):
517 | """
518 | Get the Datastore hierarchy for each Datacenter
519 |
520 | :param content: ServiceInstance Managed Object
521 | """
522 | dc_props = get_properties(content, [vim.Datacenter], ['name', 'datastore'], vim.Datacenter)
523 | ds_table = []
524 | for datacenter in dc_props:
525 | for datastore in datacenter['datastore']:
526 | dc_dict = {}
527 | dc_dict['dcname'] = datacenter['name']
528 | dc_dict['dsname'] = datastore.name
529 | ds_table.append(dc_dict)
530 | return ds_table
531 |
532 |
533 | def main():
534 | args = GetArgs()
535 | try:
536 | si = None
537 | if args.password:
538 | password = args.password
539 | else:
540 | password = getpass.getpass(prompt="Enter password for host {} and user {}: ".format(args.entity, args.user))
541 | try:
542 | si = SmartConnect(host=args.entity,
543 | user=args.user,
544 | pwd=password,
545 | port=int(args.port))
546 | except IOError, e:
547 | pass
548 | if not si:
549 | print('Could not connect to the specified host using specified username and password')
550 | return -1
551 |
552 | atexit.register(Disconnect, si)
553 | content = si.RetrieveContent()
554 |
555 | vm_props, dc_list, dc_sahost_list, dc_cl_list, cl_host_list = get_hierarchy(content)
556 | ds_table = get_datastore_hierarchy(content)
557 |
558 | if content.about.name == 'VMware vCenter Server':
559 | print("vCenter Instance detected")
560 | create_commands()
561 | create_vcenter_config(args.entity, vm_props, dc_list, dc_sahost_list, dc_cl_list, cl_host_list, ds_table)
562 | pass
563 | elif content.about.name == 'VMware ESXi':
564 | print("ESXi Host detected")
565 | create_commands()
566 | create_esxi_config(args.entity, vm_props, ds_table)
567 |
568 | except vmodl.MethodFault as e:
569 | print("Caught vmodl fault : " + e.msg)
570 | return -1
571 | except Exception as e:
572 | print("Caught exception : " + str(e))
573 | return -1
574 |
575 | return 0
576 |
577 |
578 | # Start program
579 | if __name__ == "__main__":
580 | main()
581 |
--------------------------------------------------------------------------------
/icingaexchange.yml:
--------------------------------------------------------------------------------
1 | name: pyvinga
2 | description: "file:///README.md"
3 | url: "https://github.com/lgeeklee/pyvinga"
4 | tags: Statistic,vSphere,ESXi,Virtual Machine
5 | vendor: VMware
6 | target: Operating System
7 | type: Plugin
8 | license: mit
9 | releases:
10 | -
11 | name: 1.6
12 | description: "1.6 Release: Added ESXi version to host CORE output"
13 | files:
14 | -
15 | name: pyvinga.py
16 | url: "file:///pyvinga.py"
17 | description: "Monitor vSphere"
18 | checksum: 053edd4ed9d183674cfc8b2b50d43e73
19 | -
20 | name: examples.txt
21 | url: "file:///examples.txt"
22 | description: "Examples of pyvinga.py at the command line"
23 | checksum: 8e225bdf3d8937412abcd311317ad7c7
--------------------------------------------------------------------------------
/pyvinga.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Python program that will query requested counters in vCenter and return
4 | status information for Nagios/Icinga
5 | """
6 |
7 | from __future__ import print_function
8 | from __future__ import division
9 | from pyVim.connect import SmartConnect, Disconnect
10 | from pyVmomi import vmodl, vim
11 | from datetime import timedelta, datetime
12 | from os import path
13 | from ssl import SSLError
14 | import ssl
15 | import argparse
16 | import atexit
17 | import getpass
18 | import sys
19 |
20 |
21 | # Define specific values for the Icinga return status and also create a list
22 | STATE_OK = 0
23 | STATE_WARNING = 1
24 | STATE_CRITICAL = 2
25 | STATE_UNKNOWN = 3
26 | state_tuple = 'OK', 'WARNING', 'CRITICAL', 'UNKNOWN'
27 |
28 |
29 | def GetArgs():
30 | """
31 | Supports the command-line arguments listed below.
32 | """
33 | parser = argparse.ArgumentParser(description='Process args for retrieving all the Virtual Machines')
34 | parser.add_argument('-s', '--host', required=True, action='store', help='Remote host to connect to')
35 | parser.add_argument('-i', '--insecure', action='store_true', default=False, help='Disables SSL verification')
36 | parser.add_argument('-o', '--port', type=int, default=443, action='store', help='Port to connect on')
37 | parser.add_argument('-u', '--user', required=True, action='store', help='User name to use when connecting to host')
38 | parser.add_argument('-p', '--password', required=False, action='store',
39 | help='Password to use when connecting to host')
40 | parser.add_argument('-n', '--type', required=True, action='store', help='values should be vm,host or datastore')
41 | parser.add_argument('-e', '--entity', required=True, action='store', help='One or more entities to report on')
42 | parser.add_argument('-r', '--counter', required=True, action='store', help='Performance Counter Name')
43 | parser.add_argument('-w', '--warning', required=False, action='store', default=80,
44 | help='Warning level for the counter (default: 80)')
45 | parser.add_argument('-c', '--critical', required=False, action='store', default=90,
46 | help='Critical level for the counter (default: 90)')
47 | args = parser.parse_args()
48 | return args
49 |
50 |
51 | def build_query(content, vchtime, counterId, instance, vm_moref):
52 | """
53 | Creates the query for performance stats in the correct format
54 |
55 | :param content: ServiceInstance Managed Object
56 | :param counterId: The returned integer counter Id assigned to the named performance counter
57 | :param instance: instance of the performance counter to return (typically empty but it may need to contain a value
58 | for example - with VM virtual disk queries)
59 | :param vm_moref: Managed Object Reference for the Virtual Machine
60 | """
61 | perfManager = content.perfManager
62 | metricId = vim.PerformanceManager.MetricId(counterId=counterId, instance=instance)
63 | startTime = vchtime - timedelta(seconds=60)
64 | endTime = vchtime - timedelta(seconds=40)
65 | query = vim.PerformanceManager.QuerySpec(intervalId=20, entity=vm_moref, metricId=[metricId], startTime=startTime,
66 | endTime=endTime)
67 | perfResults = perfManager.QueryPerf(querySpec=[query])
68 | if perfResults:
69 | statdata = float(sum(perfResults[0].value[0].value))
70 | return statdata
71 | else:
72 | print('ERROR: Performance results empty. Check time drift on source and vCenter server')
73 | exit(STATE_WARNING)
74 |
75 |
76 | def vm_status(vm_moref):
77 | """
78 | Obtains the overall status from the Virtual Machine
79 |
80 | :param vm_moref: Managed Object Reference for the Virtual Machine
81 | """
82 | finalOutput = str(vm_moref.overallStatus)
83 | extraOutput = '(State: ' + vm_moref.summary.runtime.powerState + ')'
84 | print_output_string(finalOutput, 'Virtual Machine Status', 'yellow', 'red', 'gray', extraOutput)
85 |
86 |
87 | def vm_core(vm_moref):
88 | """
89 | Obtains the core information for Virtual Machine (Notes, Guest, vCPU, Memory)
90 |
91 | :param vm_moref: Managed Object Reference for the Virtual Machine
92 | """
93 | vmconfig = vm_moref.summary.config
94 | if (float(vmconfig.memorySizeMB) / 1024).is_integer():
95 | vm_memory = str(vmconfig.memorySizeMB / 1024) + ' GB'
96 | else:
97 | vm_memory = str(vmconfig.memorySizeMB) + ' MB'
98 | print("{}, {}, {} vCPU(s), {} Memory".format(vmconfig.annotation, vmconfig.guestFullName,
99 | vm_moref.summary.config.numCpu, vm_memory))
100 | exit(STATE_OK)
101 |
102 |
103 | def host_core(host_moref):
104 | """
105 | Obtains the core information for ESXi Host (Hardware pCPU info, Memory)
106 |
107 | :param host_moref: Managed Object Reference for the ESXi Host
108 | """
109 | hosthardware = host_moref.summary.hardware
110 | hostversion = host_moref.config.product
111 | print("{}, {}, {} x {} ({} Cores, {} Logical), {:.0f} GB Memory".format(hostversion.fullName, hosthardware.model,
112 | hosthardware.numCpuPkgs,
113 | hosthardware.cpuModel,
114 | hosthardware.numCpuCores,
115 | hosthardware.numCpuThreads,
116 | (hosthardware.memorySize / 1024 / 1024 / 1024)))
117 | exit(STATE_OK)
118 |
119 |
120 | def host_cpu_usage(host_moref, warning, critical):
121 | """
122 | Obtains the current CPU usage of the Host
123 |
124 | :param host_moref: Managed Object Reference for the ESXi Host
125 | """
126 | host_cpu = host_moref.summary.quickStats.overallCpuUsage
127 | host_total_cpu = host_moref.summary.hardware.cpuMhz * host_moref.summary.hardware.numCpuCores
128 | final_output = (host_cpu / host_total_cpu) * 100
129 | print_output_float(final_output, 'CPU Usage', warning, critical, '%')
130 |
131 |
132 | def host_mem_usage(host_moref, warning, critical):
133 | """
134 | Obtains the current Memory usage of the Host
135 |
136 | :param host_moref: Managed Object Reference for the ESXi Host
137 | """
138 |
139 | host_memory = host_moref.summary.quickStats.overallMemoryUsage
140 | host_total_memory = host_moref.summary.hardware.memorySize / 1024 /1024
141 | final_output = (host_memory / host_total_memory) * 100
142 | print_output_float(final_output, 'Memory Usage', warning, critical, '%')
143 |
144 |
145 | def cl_status(cl_moref):
146 | """
147 | Obtains the overall status for the vSphere Cluster
148 |
149 | :param cl_moref: Managed Object Reference for the vSphere Cluster
150 | """
151 | final_output = str(cl_moref.overallStatus)
152 | print_output_string(final_output, 'Cluster Status', 'yellow', 'red', 'gray')
153 |
154 |
155 | def vm_cpu_ready(vm_moref, content, vchtime, perf_dict, warning, critical):
156 | """
157 | Obtains the CPU Ready value for the Virtual Machine
158 |
159 | :param vm_moref: Managed Object Reference for the Virtual Machine
160 | :param content: ServiceInstance Managed Object
161 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
162 | :param warning: The value to use for the print_output function to calculate whether CPU Ready is warning
163 | :param critical: The value to use for the print_output function to calculate whether CPU Ready is critical
164 | """
165 | counter_key = stat_lookup(perf_dict, 'cpu.ready.summation')
166 | statdata = build_query(content, vchtime, counter_key, "", vm_moref)
167 | final_output = (statdata / 20000 * 100)
168 | print_output_float(final_output, 'CPU Ready', warning, critical, '%')
169 |
170 |
171 | def vm_cpu_usage(vm_moref, content, vchtime, perf_dict, warning, critical):
172 | """
173 | Obtains the CPU Usage value for the Virtual Machine
174 |
175 | :param vm_moref: Managed Object Reference for the Virtual Machine
176 | :param content: ServiceInstance Managed Object
177 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
178 | :param warning: The value to use for the print_output function to calculate whether CPU Usage is warning
179 | :param critical: The value to use for the print_output function to calculate whether CPU Usage is critical
180 | """
181 | counter_key = stat_lookup(perf_dict, 'cpu.usage.average')
182 | statdata = build_query(content, vchtime, counter_key, "", vm_moref)
183 | final_output = (statdata / 100)
184 | print_output_float(final_output, 'CPU Usage', warning, critical, '%')
185 |
186 |
187 | def vm_mem_active(vm_moref, content, vchtime, perf_dict, warning, critical):
188 | """
189 | Obtains the Active Memory value for the Virtual Machine
190 |
191 | :param vm_moref: Managed Object Reference for the Virtual Machine
192 | :param content: ServiceInstance Managed Object
193 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
194 | :param warning: The value to use for the print_output function to calculate whether Active Memory is warning
195 | :param critical: The value to use for the print_output function to calculate whether Active Memory is critical
196 | """
197 | counter_key = stat_lookup(perf_dict, 'mem.active.average')
198 | statdata = build_query(content, vchtime, counter_key, "", vm_moref)
199 | final_output = (statdata / 1024)
200 | print_output_float(final_output, 'Memory Active', (warning * vm_moref.summary.config.memorySizeMB / 100),
201 | (critical * vm_moref.summary.config.memorySizeMB / 100), 'MB', '', 0, vm_moref.summary.config.memorySizeMB)
202 |
203 |
204 | def vm_mem_shared(vm_moref, content, vchtime, perf_dict, warning, critical):
205 | """
206 | Obtains the Shared Memory value for the Virtual Machine
207 |
208 | :param vm_moref: Managed Object Reference for the Virtual Machine
209 | :param content: ServiceInstance Managed Object
210 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
211 | :param warning: The value to use for the print_output function to calculate whether Shared Memory is warning
212 | :param critical: The value to use for the print_output function to calculate whether Shared Memory is critical
213 | """
214 | counter_key = stat_lookup(perf_dict, 'mem.shared.average')
215 | statdata = build_query(content, vchtime, counter_key, "", vm_moref)
216 | final_output = (statdata / 1024)
217 | print_output_float(final_output, 'Memory Shared', (warning * vm_moref.summary.config.memorySizeMB / 100),
218 | (critical * vm_moref.summary.config.memorySizeMB / 100), 'MB', '', 0, vm_moref.summary.config.memorySizeMB)
219 |
220 |
221 | def vm_mem_balloon(vm_moref, content, vchtime, perf_dict, warning, critical):
222 | """
223 | Obtains the Ballooned Memory value for the Virtual Machine
224 |
225 | :param vm_moref: Managed Object Reference for the Virtual Machine
226 | :param content: ServiceInstance Managed Object
227 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
228 | :param warning: The value to use for the print_output function to calculate whether Ballooned Memory is warning
229 | :param critical: The value to use for the print_output function to calculate whether Ballooned Memory is critical
230 | """
231 | counter_key = stat_lookup(perf_dict, 'mem.vmmemctl.average')
232 | statdata = build_query(content, vchtime, counter_key, "", vm_moref)
233 | final_output = (statdata / 1024)
234 | print_output_float(final_output, 'Memory Balloon', (warning * vm_moref.summary.config.memorySizeMB / 100),
235 | (critical * vm_moref.summary.config.memorySizeMB / 100), 'MB', '', 0, vm_moref.summary.config.memorySizeMB)
236 |
237 |
238 | def vm_ds_io(vm_moref, content, vchtime, perf_dict, warning, critical):
239 | """
240 | Obtains the Read, Write and Total Virtual Machine Datastore IOPS values.
241 | Uses the Total IOPS value to calculate status.
242 |
243 | :param vm_moref: Managed Object Reference for the Virtual Machine
244 | :param content: ServiceInstance Managed Object
245 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
246 | :param warning: The value to use for the print_output function to calculate whether IOPS are warning
247 | :param critical: The value to use for the print_output function to calculate whether IOPS are critical
248 | """
249 | counter_key_read = stat_lookup(perf_dict, 'datastore.numberReadAveraged.average')
250 | counter_key_write = stat_lookup(perf_dict, 'datastore.numberWriteAveraged.average')
251 | statdata_read = build_query(content, vchtime, counter_key_read, "*", vm_moref)
252 | statdata_write = build_query(content, vchtime, counter_key_write, "*", vm_moref)
253 | statdata_total = statdata_read + statdata_write
254 | print_output_float(statdata_total, 'Datastore IOPS', warning, critical, 'IOPS', '', 0, 5000)
255 |
256 |
257 | def vm_ds_latency(vm_moref, content, vchtime, perf_dict, warning, critical):
258 | """
259 | Obtains the Read, Write and Total Virtual Machine Datastore Latency values.
260 | Uses the Total IOPS value to calculate status.
261 |
262 | :param vm_moref: Managed Object Reference for the Virtual Machine
263 | :param content: ServiceInstance Managed Object
264 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
265 | :param warning: The value to use for the print_output function to calculate whether Latency is warning
266 | :param critical: The value to use for the print_output function to calculate whether Latency is critical
267 | """
268 | counter_key_read = stat_lookup(perf_dict, 'datastore.totalReadLatency.average')
269 | counter_key_write = stat_lookup(perf_dict, 'datastore.totalWriteLatency.average')
270 | statdata_read = build_query(content, vchtime, counter_key_read, "*", vm_moref)
271 | statdata_write = build_query(content, vchtime, counter_key_write, "*", vm_moref)
272 | statdata_total = statdata_read + statdata_write
273 | print_output_float(statdata_total, 'Datastore Latency', warning, critical, 'ms', '', 0, 100)
274 |
275 |
276 | def vm_net_usage(vm_moref, content, vchtime, perf_dict, warning, critical):
277 | """
278 | Obtains the Tx and Rx Virtual Machine Network Usage values.
279 | Uses the Total Network Usage value to calculate status.
280 |
281 | :param vm_moref: Managed Object Reference for the Virtual Machine
282 | :param content: ServiceInstance Managed Object
283 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
284 | :param warning: The value to use for the print_output function to calculate whether Network Usage is warning
285 | :param critical: The value to use for the print_output function to calculate whether Network Usage is critical
286 | """
287 | counter_key_read = stat_lookup(perf_dict, 'net.received.average')
288 | counter_key_write = stat_lookup(perf_dict, 'net.transmitted.average')
289 | statdata_rx = build_query(content, vchtime, counter_key_read, "", vm_moref)
290 | statdata_tx = build_query(content, vchtime, counter_key_write, "", vm_moref)
291 | statdata_total = (statdata_rx + statdata_tx) * 8 / 1024
292 | print_output_float(statdata_total, 'Network Usage', warning, critical, 'Mbps', '', 0, 1000)
293 |
294 |
295 | def ds_space(ds_moref, warning, critical):
296 | """
297 | Obtains the Datastore space information
298 | :param ds_moref: Managed Object Reference for the Datastore
299 | :param warning: The value to use for the print_output function to calculate whether Datastore space is warning
300 | :param critical: The value to use for the print_output function to calculate whether Datastore space is critical
301 | """
302 | datastore_capacity = float(ds_moref.summary.capacity / 1024 / 1024 / 1024)
303 | datastore_free = float(ds_moref.summary.freeSpace / 1024 / 1024 / 1024)
304 | datastore_used_pct = ((1 - (datastore_free / datastore_capacity)) * 100)
305 | extraOutput = "(Used {:.1f} GB of {:.1f} GB)".format((datastore_used_pct * datastore_capacity / 100),
306 | datastore_capacity)
307 | print_output_float(datastore_used_pct, 'Datastore Used Space', warning, critical, '%', extraOutput)
308 |
309 |
310 | def ds_status(ds_moref):
311 | """
312 | Obtains the overall status for the Datastore
313 |
314 | :param ds_moref: Managed Object Reference for the Datastore
315 | """
316 | final_output = str(ds_moref.overallStatus)
317 | extraOutput = '(Type: ' + ds_moref.summary.type + ')'
318 | print_output_string(final_output, 'Datastore Status', 'yellow', 'red', 'gray', extraOutput)
319 |
320 |
321 | def stat_lookup(perf_dict, counter_name):
322 | """
323 | Performance the lookup of the supplied counter name against the dictionary and returns a counter Id
324 |
325 | :param perf_dict: The array containing the performance dictionary (with counters and IDs)
326 | :param counter_name: The counter name in the correct format for the dictionary
327 | """
328 | counter_key = perf_dict[counter_name]
329 | return counter_key
330 |
331 |
332 | def get_properties(content, viewType, props, specType):
333 | """
334 | Obtains a list of specific properties for a particular Managed Object Reference data object.
335 |
336 | :param content: ServiceInstance Managed Object
337 | :param viewType: Type of Managed Object Reference that should populate the View
338 | :param props: A list of properties that should be retrieved for the entity
339 | :param specType: Type of Managed Object Reference that should be used for the Property Specification
340 | :return:
341 | """
342 | # Get the View based on the viewType
343 | objView = content.viewManager.CreateContainerView(content.rootFolder, viewType, True)
344 | # Build the Filter Specification
345 | tSpec = vim.PropertyCollector.TraversalSpec(name='tSpecName', path='view', skip=False, type=vim.view.ContainerView)
346 | pSpec = vim.PropertyCollector.PropertySpec(all=False, pathSet=props, type=specType)
347 | oSpec = vim.PropertyCollector.ObjectSpec(obj=objView, selectSet=[tSpec], skip=False)
348 | pfSpec = vim.PropertyCollector.FilterSpec(objectSet=[oSpec], propSet=[pSpec], reportMissingObjectsInResults=False)
349 | retOptions = vim.PropertyCollector.RetrieveOptions()
350 | # Retrieve the properties and look for a token coming back with each RetrievePropertiesEx call
351 | # If the token is present it indicates there are more items to be returned.
352 | totalProps = []
353 | retProps = content.propertyCollector.RetrievePropertiesEx(specSet=[pfSpec], options=retOptions)
354 | totalProps += retProps.objects
355 | while retProps.token:
356 | retProps = content.propertyCollector.ContinueRetrievePropertiesEx(token=retProps.token)
357 | totalProps += retProps.objects
358 | objView.Destroy()
359 | # Turn the output in totalProps into a usable dictionary of values
360 | gpOutput = []
361 | for eachProp in totalProps:
362 | propDic = {}
363 | for prop in eachProp.propSet:
364 | propDic[prop.name] = prop.val
365 | propDic['moref'] = eachProp.obj
366 | gpOutput.append(propDic)
367 | return gpOutput
368 |
369 |
370 | def print_output_float(finalOutput, statName, warnValue, critValue, suffix, extraOutput='', min_value=0, max_value=100):
371 | """
372 | Prints the formatted output for Icinga based on supplied warning and critical values.
373 | Used for functions where a float is supplied for comparison.
374 |
375 | :param finalOutput: The final calculated performance value for the counter
376 | :param statName: The friendly name for the performance statistic
377 | :param warnValue: The value used to calculate the warning threshold for status change
378 | :param critValue: The value used to calculate the critical threshold for status change
379 | :param suffix: The performance value suffix (e.g. MB, GB, %)
380 | :param extraOutput: Any additional output that is displayed after the core performance information
381 | """
382 | if finalOutput >= critValue:
383 | print("{0} - {1} is {2:.1f}{3} {4} | '{1}'={2:.1f}{3};{5};{6}".format(state_tuple[STATE_CRITICAL], statName,
384 | finalOutput, suffix, extraOutput,
385 | warnValue, critValue, min_value, max_value))
386 | exit(STATE_CRITICAL)
387 | elif finalOutput >= warnValue:
388 | print("{0} - {1} is {2:.1f}{3} {4} | '{1}'={2:.1f}{3};{5};{6}".format(state_tuple[STATE_WARNING], statName,
389 | finalOutput, suffix, extraOutput,
390 | warnValue, critValue, min_value, max_value))
391 | exit(STATE_WARNING)
392 | else:
393 | print("{0} - {1} is {2:.1f}{3} {4} | '{1}'={2:.1f}{3};{5};{6};{7};{8}".format(state_tuple[STATE_OK], statName,
394 | finalOutput, suffix, extraOutput,
395 | warnValue, critValue, min_value, max_value))
396 | exit(STATE_OK)
397 |
398 |
399 | def print_output_string(finalOutput, statName, warnValue, critValue, unkValue, extraOutput=''):
400 | """
401 | Prints the formatted output for Icinga based on supplied warning and critical values.
402 | Used for functions where a text string is supplied for comparison.
403 |
404 | :param finalOutput: The final calculated performance value for the counter
405 | :param statName: The friendly name for the performance statistic
406 | :param warnValue: The text string used to calculate the warning threshold for status change
407 | :param critValue: The text string used to calculate the critical threshold for status change
408 | :param unkValue: The text string used to calculate the unknown threshold for status change
409 | :param extraOutput: Any additional output that is displayed after the core performance information
410 | """
411 | if finalOutput == critValue:
412 | print("{} - {} is {} {}".format(state_tuple[STATE_CRITICAL], statName, finalOutput, extraOutput))
413 | exit(STATE_CRITICAL)
414 | elif finalOutput == warnValue:
415 | print("{} - {} is {} {}".format(state_tuple[STATE_WARNING], statName, finalOutput, extraOutput))
416 | exit(STATE_WARNING)
417 | elif finalOutput == unkValue:
418 | print("{} - {} is {} {}".format(state_tuple[STATE_UNKNOWN], statName, finalOutput, extraOutput))
419 | exit(STATE_WARNING)
420 | else:
421 | print("{} - {} is {} {}".format(state_tuple[STATE_OK], statName, finalOutput, extraOutput))
422 | exit(STATE_OK)
423 |
424 |
425 | def create_perf_dictionary(content):
426 | """
427 | Checks whether the connection is to an ESXi host or vCenter and calls the write_perf_dictionary
428 | function with the relevant file name.
429 |
430 | :param content: ServiceInstance Managed Object
431 | """
432 | if content.about.name == 'VMware vCenter Server':
433 | perf_dict = write_perf_dictionary(content, '/tmp/vcenter_perfdic.txt')
434 | # perf_dict = write_perf_dictionary(content, 'c:\\temp\\vcenter_perfdic.txt')
435 | elif content.about.name == 'VMware ESXi':
436 | perf_dict = write_perf_dictionary(content, '/tmp/host_perfdic.txt')
437 | # perf_dict = write_perf_dictionary(content, 'c:\\temp\\host_perfdic.txt')
438 | return perf_dict
439 |
440 |
441 | def write_perf_dictionary(content, file_perf_dic):
442 | """
443 | Checks whether the performance dictionary is older that 7 days. If it is it creates a new one.
444 | This dictionary is read into the array and used in the functions that require perf_dict.
445 | NOTE: This is faster than doing a lookup live with a ServiceInstance Managed Object for every performance query.
446 |
447 | :param content: ServiceInstance Managed Object
448 | :param file_perf_dic: file name supplied by calling function (based on ESXi or vCenter connection)
449 | :return:
450 | """
451 | if not path.exists(file_perf_dic) or datetime.fromtimestamp(path.getmtime(file_perf_dic)) < (datetime.now() - timedelta(days=7)):
452 | # Get all the vCenter performance counters
453 | perf_dict = {}
454 | perfList = content.perfManager.perfCounter
455 | f = open(file_perf_dic, mode='w')
456 | for counter in perfList:
457 | counter_full = "{}.{}.{}".format(counter.groupInfo.key, counter.nameInfo.key, counter.rollupType)
458 | perf_dict[counter_full] = counter.key
459 | f.write(counter_full + ',' + str(perf_dict[counter_full]) + '\n')
460 | f.close()
461 | else:
462 | perf_dict = {}
463 | f = open(file_perf_dic, mode='r')
464 | for line in f:
465 | perf_dict[line.split(',')[0]] = int(line.split(',')[1])
466 | f.close()
467 | return perf_dict
468 |
469 |
470 | def main():
471 | args = GetArgs()
472 | try:
473 | # disable SSL verification if requested
474 | context = None
475 | if args.insecure:
476 | context = ssl._create_unverified_context()
477 | entity = args.entity
478 | if args.counter != 'core' and args.counter != 'status':
479 | warning = float(args.warning)
480 | critical = float(args.critical)
481 | si = None
482 | if args.password:
483 | password = args.password
484 | else:
485 | password = getpass.getpass(prompt="Enter password for host {} and user {}: ".format(args.host, args.user))
486 |
487 | # Set stderr to log /dev/null instead of the screen to prevent warnings contaminating output
488 | # NOTE: This is only in place until a more suitable method to deal with the latest certificate warnings
489 | f = open('/dev/null', "w")
490 | # f = open('c:\\temp\\dummy', "w")
491 | original_stderr = sys.stderr
492 | sys.stderr = f
493 | try:
494 | si = SmartConnect(host=args.host,
495 | user=args.user,
496 | pwd=password,
497 | port=int(args.port),
498 | sslContext=context)
499 | except SSLError as e:
500 | print('Could not verify SSL certificate, use -i / --insecure to skip checking')
501 | return -1
502 | except IOError:
503 | pass
504 | finally:
505 | sys.stderr = original_stderr
506 |
507 | if not si:
508 | print('Could not connect to the specified host using specified username and password')
509 | return -1
510 |
511 | atexit.register(Disconnect, si)
512 | content = si.RetrieveContent()
513 | # Get vCenter date and time for use as baseline when querying for counters
514 | vchtime = si.CurrentTime()
515 |
516 | perf_dict = create_perf_dictionary(content)
517 |
518 | if args.type == 'vm':
519 | #Find VM supplied as arg and use Managed Object Reference (moref) for the PrintVmInfo
520 | vmProps = get_properties(content, [vim.VirtualMachine], ['name', 'runtime.powerState'], vim.VirtualMachine)
521 | for vm in vmProps:
522 | if (vm['name'] == entity) and (vm['runtime.powerState'] == "poweredOn"):
523 | vm_moref = vm['moref']
524 | if args.counter == 'core':
525 | vm_core(vm_moref)
526 | elif args.counter == 'status':
527 | vm_status(vm_moref)
528 | elif args.counter == 'cpu.ready':
529 | vm_cpu_ready(vm_moref, content, vchtime, perf_dict, warning, critical)
530 | elif args.counter == 'cpu.usage':
531 | vm_cpu_usage(vm_moref, content, vchtime, perf_dict, warning, critical)
532 | elif args.counter == 'mem.active':
533 | vm_mem_active(vm_moref, content, vchtime, perf_dict, warning, critical)
534 | elif args.counter == 'mem.shared':
535 | vm_mem_shared(vm_moref, content, vchtime, perf_dict, warning, critical)
536 | elif args.counter == 'mem.balloon':
537 | vm_mem_balloon(vm_moref, content, vchtime, perf_dict, warning, critical)
538 | elif args.counter == 'datastore.io':
539 | vm_ds_io(vm_moref, content, vchtime, perf_dict, warning, critical)
540 | elif args.counter == 'datastore.latency':
541 | vm_ds_latency(vm_moref, content, vchtime, perf_dict, warning, critical)
542 | elif args.counter == 'network.usage':
543 | vm_net_usage(vm_moref, content, vchtime, perf_dict, warning, critical)
544 | else:
545 | print('ERROR: No supported counter found')
546 | exit(STATE_UNKNOWN)
547 | elif (vm['name'] == entity) and ((vm['runtime.powerState'] == "poweredOff") or (vm['runtime.powerState'] == "suspended")):
548 | vm_moref = vm['moref']
549 | if args.counter == 'core':
550 | vm_core(vm_moref)
551 | elif args.counter == 'status':
552 | vm_status(vm_moref)
553 | else:
554 | print('ERROR: Virtual Machine is powered off')
555 | exit(STATE_UNKNOWN)
556 |
557 | elif args.type == 'host':
558 | hostProps = get_properties(content, [vim.HostSystem], ['name'], vim.HostSystem)
559 | for host in hostProps:
560 | if host['name'] == entity:
561 | host_moref = host['moref']
562 | if args.counter == 'core':
563 | host_core(host_moref)
564 | elif args.counter == 'cpu.usage':
565 | host_cpu_usage(host_moref, warning, critical)
566 | elif args.counter == 'mem.usage':
567 | host_mem_usage(host_moref, warning, critical)
568 | else:
569 | print('ERROR: No supported counter found')
570 | exit(STATE_UNKNOWN)
571 |
572 | elif args.type == 'datastore':
573 | dsProps = get_properties(content, [vim.Datastore], ['name'], vim.Datastore)
574 | for datastore in dsProps:
575 | if datastore['name'] == entity:
576 | ds_moref = datastore['moref']
577 | if args.counter == 'status':
578 | ds_status(ds_moref)
579 | elif args.counter == 'space':
580 | ds_space(ds_moref, warning, critical)
581 | else:
582 | print('ERROR: No supported counter found')
583 | exit(STATE_UNKNOWN)
584 |
585 | elif args.type == 'cluster':
586 | clProps = get_properties(content, [vim.ClusterComputeResource], ['name'], vim.ClusterComputeResource)
587 | for cluster in clProps:
588 | if cluster['name'] == entity:
589 | cl_moref = cluster['moref']
590 | if args.counter == 'status':
591 | cl_status(cl_moref)
592 | else:
593 | print('ERROR: No supported counter found')
594 | exit(STATE_UNKNOWN)
595 |
596 | else:
597 | print('ERROR: No supported Entity type provided')
598 |
599 | except vmodl.MethodFault as e:
600 | print("Caught vmodl fault : " + e.msg)
601 | return -1
602 | except Exception as e:
603 | print("Caught exception : " + str(e))
604 | return -1
605 |
606 | return 0
607 |
608 | # Start program
609 | if __name__ == "__main__":
610 | main()
611 |
--------------------------------------------------------------------------------