-
Notifications
You must be signed in to change notification settings - Fork 85
/
vmware_vm.py
309 lines (260 loc) · 13.5 KB
/
vmware_vm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#!/usr/bin/env python
"""vmware_vm - Exports JSON files with CPU and RAM data for VMware VMs
# Requires VMware Python SDK: pyvmomi
# https://code.google.com/p/pysphere/
# pip install pyvmomi
"""
from credentials import VMWARE_VCENTER_USERNAME
from credentials import VMWARE_VCENTER_PASSWORD
import operator
import time
import json
import logging.config
from pyVim.connect import SmartConnect
from pchelper import collect_properties
from pchelper import get_container_view
import pyVmomi
import ssl
__author__ = '[email protected] (Scott Vintinner)'
# =================================SETTINGS======================================
VCENTER_SERVER = "vcenter"
VCENTER_USERNAME = VMWARE_VCENTER_USERNAME
VCENTER_PASSWORD = VMWARE_VCENTER_PASSWORD
SAMPLE_INTERVAL = 60
MAX_DATAPOINTS = 30
MAX_VM_RESULTS = 11 # Number of VMs to get data (should match html file)
EXCLUDE_VM = ["NTNX"] # VMs with any of the items in this list in their
# name will be excluded from results (ex: "NTNX-123")
# ===============================================================================
class MonitorJSON:
"""This is a simple class passed to Monitor threads so we can access the current JSON data in that thread"""
def __init__(self):
# Set the default empty values for all VMs
vms = []
for i in range(MAX_VM_RESULTS):
vms.append({"name": "----", "status": 0, "cpu": [0, 0, 0, 0]})
self.json = json.dumps({"vms": vms}, indent=4)
self.conn = None # Will store the connection object for vSphere
class VMwareVM:
all_vms = [] # Static array containing all hosts
def __init__(self, managed_object_reference, name):
self.managed_object_reference = managed_object_reference
self.name = name
self.cpu_datapoints = []
self.cpu_count = 1 # Number of vCPUs
self.host_cpu_mhz = 1.0 # Host CPU speed
self.heartbeat_status = 0
self.relative_weight = 0.0
self.__class__.all_vms.append(self) # Add self to static array
@classmethod
def find_by_name(cls, managed_object_reference, name):
for vm in cls.all_vms:
if vm.name == name:
return vm
# if not found, create one and return it instead
return VMwareVM(managed_object_reference, name)
def update_relative_weight(self):
"""The relative weight is used to determine how much we want to see the data of this VM."""
self.relative_weight = 1
# Add up all of the historical cpu datapoints (higher CPU = more weight)
for i in self.cpu_datapoints:
self.relative_weight += i
# Multiply by the status value (so VMs with red alarm have most weight)
self.relative_weight *= (self.heartbeat_status * 10)
class VMwareHost:
"""In order to calculate CPU used percent for VMs, we need to know Mhz speed of Host. We update this list
to make our search faster. It is stored as a static array to persist across calls to this module."""
all_hosts = [] # Static array containing all hosts
def __init__(self, managed_object_reference, name, hz):
self.managed_object_reference = managed_object_reference
self.name = name
self.cpu_mhz = int(hz / (1000 * 1000)) # CPU speed
self.__class__.all_hosts.append(self) # Add self to static array
@classmethod
def get_mhz_by_host(cls, managed_object_reference):
for host in cls.all_hosts:
if host.managed_object_reference == managed_object_reference:
return host.cpu_mhz
def connect_vcenter():
"""This function will connect to the specified vCenter server. If it fails, it will retry"""
logger = logging.getLogger(__name__)
# Disable certificate verification otherwise it will error
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
ssl_context.verify_mode = ssl.CERT_NONE
server_instance = None
try:
logger.debug("Connecting to: " + VCENTER_SERVER)
server_instance = SmartConnect(host=VCENTER_SERVER, user=VCENTER_USERNAME,
pwd=VCENTER_PASSWORD, sslContext=ssl_context)
server_instance._stub.connectionPoolTimeout = -1 # Turn the connection timeout off (default 900 sec)
except Exception as error:
logger.error("Error connecting to " + VCENTER_SERVER + str(error))
return server_instance
def generate_json(vmware_monitor):
"""This is the main function. It will connect to the vCenter server, obtain perf data and output json"""
logger = logging.getLogger("vmware_vm")
if vmware_monitor.conn is None:
vmware_monitor.conn = connect_vcenter() # Connect to vCenter server
if vmware_monitor.conn is None:
logger.warning("Unable to connect to " + VCENTER_SERVER + " will retry in " +
str(SAMPLE_INTERVAL) + " seconds.")
vmware_monitor.json = json.dumps({"vms": [{"error": "Unable to connect to " + VCENTER_SERVER +
" will retry in " + str(SAMPLE_INTERVAL) +
" seconds."}]}, indent=4)
return vmware_monitor # Could not connect so return
# Final test if we're connected
try:
if vmware_monitor.conn.content.sessionManager.currentSession:
logger.debug("Connected to " + VCENTER_SERVER + " at " +
str(vmware_monitor.conn.content.sessionManager.currentSession.loginTime))
except Exception as error:
logger.error("Final test: Error connecting to " + VCENTER_SERVER + str(error))
vmware_monitor.json = json.dumps({"vms": [{"error": "Unable to connect to " + VCENTER_SERVER +
" will retry in " + str(SAMPLE_INTERVAL) +
" seconds."}]}, indent=4)
return vmware_monitor # Could not connect so return
#
# ---------------------------------------------------------------------------------------------------------------
#
# First we want to grab a list of all the hosts so we can determine the clock speed of the CPUs
# API: HostSystem - https://pubs.vmware.com/vsphere-51/index.jsp#com.vmware.wssdk.apiref.doc/vim.HostSystem.html
#
# ---------------------------------------------------------------------------------------------------------------
try:
# Fast way of getting Host properties using PropertyCollector
# https://github.com/vmware/pyvmomi/blob/master/docs/vmodl/query/PropertyCollector.rst
logger.debug("Collecting properties for HostSystem")
container_view = get_container_view(vmware_monitor.conn, obj_type=[pyVmomi.vim.HostSystem])
query = ["name", "hardware.cpuInfo.hz"]
host_props = collect_properties(vmware_monitor.conn, container_view,
pyVmomi.vim.HostSystem, query, include_mors=True)
except Exception as error:
logger.error("Error collecting VMware Host data." + str(error))
vmware_monitor.json = json.dumps({"vms": [{"error": "Error retrieving VMware Host data" +
str(error)}]}, indent=4)
return vmware_monitor
for prop_set in host_props:
# The properties aren't always returned in the order you expect, so we have to match them up
host_mor = prop_set["obj"]
host_name = prop_set["name"]
host_hz = prop_set["hardware.cpuInfo.hz"]
logger.debug("Name: " + host_name + " cpuInfo.hz: " + str(host_hz))
# Create host object so we can find Mhz later (object is stored in class array all_hosts)
VMwareHost(host_mor, host_name, host_hz)
# ---------------------------------------------------------------------------------------------------------------
#
# Next we query for the VM stats we are looking for.
#
# ---------------------------------------------------------------------------------------------------------------
# API: VirtualMachine
# https://pubs.vmware.com/vsphere-50/index.jsp#com.vmware.wssdk.apiref.doc_50/vim.VirtualMachine.html
#
# summary.overallStatus: general "health" value: gray, green, red, yellow
# summary.quickStats.overallCpuUsage: Amount of CPU actually granted to the VM in Mhz
# summary.quickStats.staticCpuEntitlement: Max CPU possible for the VM in Mhz
# summary.quickStats.guestMemoryUsage: Active memory usage of the VM in MB.
# summary.quickStats.staticMemoryEntitlement: Max CPU possible for the VM in MB
# config.hardware.numCPU: Number of virtual CPUs present in this virtual machine.
# runtime.host: The host that is responsible for running a virtual machine.
try:
# Fast way of getting VM properties using PropertyCollector
# https://github.com/vmware/pyvmomi/blob/master/docs/vmodl/query/PropertyCollector.rst
logger.debug("Collecting properties for VirtualMachine")
container_view = get_container_view(vmware_monitor.conn, obj_type=[pyVmomi.vim.VirtualMachine])
query = [
"name",
"summary.overallStatus",
"summary.quickStats.overallCpuUsage",
"config.hardware.numCPU", # This number is vCPU
"runtime.host"
]
props = collect_properties(vmware_monitor.conn, container_view,
pyVmomi.vim.VirtualMachine, query, include_mors=True)
except Exception as error:
logger.error("Error collecting VMware VirtualMachine data." + str(error))
vmware_monitor.json = json.dumps({"vms": [{"error": "Error retrieving VMware VirtualMachine data" +
str(error)}]}, indent=4)
return vmware_monitor
# Loop through all of the VMs
for prop_set in props:
mor = prop_set["obj"] # managed_object_reference
vm_name = prop_set["name"]
vm_status = prop_set["summary.overallStatus"]
vm_cpu = prop_set["summary.quickStats.overallCpuUsage"]
vm_cpu_count = prop_set["config.hardware.numCPU"]
vm_host_mor = prop_set["runtime.host"] # managed_object_reference for its host
# Exclude any items by our list of search terms EXCLUDE_VM
if any(substring in vm_name for substring in EXCLUDE_VM):
logger.debug("Excluding: " + vm_name)
continue
else:
logger.debug("VM: " + vm_name + " Status: " + str(vm_status) + " CPU: " + str(vm_cpu))
# Check to see if this VM is in our list or create one if not found
vm = VMwareVM.find_by_name(mor, vm_name)
if vm_status == "green":
vm.heartbeat_status = 1
elif vm_status == "yellow":
vm.heartbeat_status = 2
elif vm_status == "red":
vm.heartbeat_status = 3
else:
vm.heartbeat_status = 0
# Store the cpu data in the object
if len(vm.cpu_datapoints) == 0:
vm.cpu_datapoints = [vm_cpu]
else:
vm.cpu_datapoints.append(vm_cpu)
# If we already have the max number of datapoints in our list, delete the oldest item
if len(vm.cpu_datapoints) >= MAX_DATAPOINTS:
del (vm.cpu_datapoints[0])
vm.host_cpu_mhz = VMwareHost.get_mhz_by_host(vm_host_mor) # Get the host hz per CPU
vm.cpu_count = vm_cpu_count
# Update ranking value of this VM to determine if we should show it
vm.update_relative_weight()
# Once we have finished updating our VM data, grab the top MAX_VM_RESULTS and output the JSON
# Sort by relative weight
VMwareVM.all_vms.sort(key=operator.attrgetter('relative_weight'), reverse=True)
# If there are fewer VMs than we've asked it to display, fix the MAX_VM_RESULTS
global MAX_VM_RESULTS
if len(VMwareVM.all_vms) < MAX_VM_RESULTS:
MAX_VM_RESULTS = len(VMwareVM.all_vms)
vms = []
for i in range(MAX_VM_RESULTS):
vms.append({
"name": VMwareVM.all_vms[i].name,
"status": VMwareVM.all_vms[i].heartbeat_status,
"cpu": VMwareVM.all_vms[i].cpu_datapoints,
"cpu_count": VMwareVM.all_vms[i].cpu_count,
"host_cpu_mhz": VMwareVM.all_vms[i].host_cpu_mhz,
})
vmware_monitor.json = json.dumps({"vms": vms})
logger.debug(vmware_monitor.json)
return vmware_monitor
# ======================================================
# __main__
#
# If you run this module by itself, it will instantiate
# the MonitorJSON class and start an infinite loop
# printing data.
# ======================================================
#
if __name__ == '__main__':
# When run by itself, we need to create the logger object (which is normally created in webserver.py)
try:
f = open("log_settings.json", 'rt')
log_config = json.load(f)
f.close()
logging.config.dictConfig(log_config)
except FileNotFoundError as e:
print("Log configuration file not found: " + str(e))
logging.basicConfig(level=logging.DEBUG) # fallback to basic settings
except json.decoder.JSONDecodeError as e:
print("Error parsing logger config file: " + str(e))
raise
monitor = MonitorJSON()
while True:
main_logger = logging.getLogger(__name__)
generate_json(monitor)
# Wait X seconds for the next iteration
main_logger.debug("Waiting for " + str(SAMPLE_INTERVAL) + " seconds")
time.sleep(SAMPLE_INTERVAL)