├── 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 | Example output 12 | 13 | Example output 14 | 15 | Example output 16 | 17 | Example output 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 | --------------------------------------------------------------------------------