Merge pull request #28 from recklessop/zvma-10-dev

Zvma 10 dev
This commit is contained in:
2024-05-30 21:07:55 -04:00
committed by GitHub
20 changed files with 2049 additions and 493 deletions
+5
View File
@@ -1,6 +1,7 @@
logs/*
.env/*
.DS_Store
uuid.txt
app/metrics
app/metrics.txt
app/statsmetrics
@@ -10,4 +11,8 @@ app/threads.txt
app/vrametrics
app/vrametrics.txt
app/__pycache__/*
app/microsoft.gpg
app/logs/*
app/zvma10/__pycache__/*
app/zvma9_7/__pycache__/*
app/temp.sh
+31 -2
View File
@@ -1,9 +1,38 @@
FROM python:3.13.0b1-slim
FROM python:3.12.3-slim
EXPOSE 9999
# Install system dependencies
RUN apt-get update \
&& apt-get install -y \
curl \
gcc \
libffi-dev \
libssl-dev \
python3-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install Rust and Cargo using curl with IPv4 only
RUN CURL_IPRESOLVE=4 curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
WORKDIR /usr/src/app
COPY app/* /usr/src/app/
# Set PYTHONPATH to include /usr/src/app
ENV PYTHONPATH=/usr/src/app
# Copy the zerto exporter into the container
COPY app /usr/src/app/
# Delete uuid.txt file if it exists
RUN [ -f uuid.txt ] && rm uuid.txt || echo "No uuid.txt file to delete"
# Install Python dependencies
# Set environment variable for PyO3 compatibility
ENV PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
# Entry point for the container
CMD ["python", "python-node-exporter.py"]
+3
View File
@@ -0,0 +1,3 @@
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmcCJDZlMmZlNTc4LTI2NTgtNDVlZS04MDA2LTVmMGUzNTQyMzFmZAACKlszLCJjNTQzODQ3Yy1iOGQ1LTQwZDAtOTU5Yy0zNWIxNGVmM2NhNjkiXQAABiBdsEOQWVnk-qd-erTO48YJLKxiztWySeNQ45V6y45fkw
File diff suppressed because it is too large Load Diff
+28 -5
View File
@@ -1,5 +1,28 @@
requests
tinydb
tinydb-storage
pyVmomi
pyVim
annotated-types==0.6.0
async-timeout==4.0.3
backoff==2.2.1
boto3==1.28.63
botocore==1.31.63
cachetools==5.3.1
certifi==2023.7.22
charset-normalizer==3.3.0
docopt==0.6.2
idna==3.4
jmespath==1.0.1
monotonic==1.6
posthog==3.0.2
prompt-toolkit==3.0.39
pydantic
pyflakes==3.1.0
Pygments==2.16.1
python-dateutil==2.8.2
pyvim==3.0.3
pyvmomi==8.0.2.0
redis==5.0.1
requests==2.32.0
s3transfer==0.7.0
six==1.16.0
tinydb==4.8.0
typing_extensions==4.8.0
urllib3==2.0.6
wcwidth==0.2.8
+2 -2
View File
@@ -1,5 +1,5 @@
# version.py
VERSION = "1.2.0"
VERSION = "2.1.0"
def main():
# Put your main program code here
@@ -7,4 +7,4 @@ def main():
if __name__ == '__main__':
# This code will be executed only when the file is run as the main program
main()
main()
+4
View File
@@ -0,0 +1,4 @@
print("Initializing vcenter package...")
#from .zvma import zvm
from .vcenter import vcsite
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+206
View File
@@ -0,0 +1,206 @@
# Class for holding variables related to a site.
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim, vmodl
import ssl
import datetime
import logging
import socket
from logging.handlers import RotatingFileHandler
class vcsite:
def __init__(self, host, username, password, port=443, verify_ssl=False, loglevel="INFO", logger=None):
self.host = host
self.port = port
self.username = username
self.password = password
self.verify_ssl = verify_ssl
self.version = None
self.__conn__ = None
self.LOGLEVEL = loglevel.upper()
self.log = None
if logger is None:
#set log line format including container_id
container_id = str(socket.gethostname())
log_formatter = logging.Formatter("%(asctime)s;%(levelname)s;%(threadName)s;%(message)s", "%Y-%m-%d %H:%M:%S")
log_handler = RotatingFileHandler(filename=f"./logs/Log-{container_id}.log", maxBytes=1024*1024*100, backupCount=5)
log_handler.setFormatter(log_formatter)
self.log = logging.getLogger("vCenter Module")
self.log.setLevel(self.LOGLEVEL)
self.log.addHandler(log_handler)
else:
self.log = logger
def connect(self):
self.log.info(f"Log Level set to {self.LOGLEVEL}")
if self.__conn__ is None:
context = ssl.create_default_context()
if not self.verify_ssl:
self.log.debug("dont verify SSL")
# Create an SSL context without certificate verification
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# connect to vCenter Server
si = None
try:
self.__conn__ = SmartConnect(host=self.host, user=self.username, pwd=self.password, sslContext=context)
about_info = self.__conn__.content.about
version = about_info.version
self.version = version
self.log.debug("Connected to vCenter Server %s", self.host)
except Exception as e:
self.log.error(f"Error connecting to vCenter Server: {e}")
def version(self):
return self.version
def get_cpu_mem_used(self, vra):
if vra == None:
self.log.debug("Get_cpu_mem_used called with no vm name...returning no data")
return
if self.__conn__ == None:
self.log.debug("Trying to get VRA stats without vCenter connection, trying to connect")
self.connect()
# get the root folder of the vCenter Server
try:
content = self.__conn__.RetrieveContent()
root_folder = content.rootFolder
except:
self.log.debug("Could not get content from vCenter when trying to get VRA stats")
# create a view for all VMs on the vCenter Server
view_manager = content.viewManager
vm_view = view_manager.CreateContainerView(root_folder, [vim.VirtualMachine], True)
vm = None
for vm_obj in vm_view.view:
if str(vm_obj.name) == str(vra):
vm = vm_obj
if vm is not None:
self.log.debug(f"Found VRA VM in vCenter with name {vm.name}")
# get the CPU usage and memory usage for the VM
cpu_usage_mhz = vm.summary.quickStats.overallCpuUsage
memory_usage_mb = vm.summary.quickStats.guestMemoryUsage
# print the CPU and memory usage for the VM
self.log.info(f"VM {vm.name} has CPU usage of {cpu_usage_mhz} MHz and memory usage of {memory_usage_mb} MB")
return [cpu_usage_mhz, memory_usage_mb]
else:
self.log.debug(f"{vm_obj.name} is not a VRA")
raise ValueError("No VRA Found")
def get_write_iops(self, vm):
try:
content = self.__conn__.RetrieveContent()
except:
self.log.debug("Could not get content from vCenter when trying to get VRA stats")
# Find the virtual machine by name
vm_name = str(vm)
vm = None
for obj in content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True).view:
if obj.name == vm_name:
vm = obj
break
if vm is None:
print(f"Virtual machine '{vm_name}' not found")
return
# Get performance manager
perf_manager = content.perfManager
# Define the metric ID for write IOPS (counterId = 6)
metric_id = vim.PerformanceManager.MetricId(counterId=6, instance="")
# calculate the last 60 seconds
end_time = datetime.datetime.now()
start_time = end_time - datetime.timedelta(seconds=60)
# Create a query specification for roll-up data
query_spec = vim.PerformanceManager.QuerySpec(
entity=vm,
metricId=[metric_id],
format="normal",
startTime=start_time,
endTime=end_time,
intervalId=20, # Use an appropriate interval for the roll-up data
)
# Query the performance statistics
result = perf_manager.QueryStats(querySpec=[query_spec])
if result:
# Get the average write IOPS for the last 60 seconds
average_write_iops = sum(result[0].value[0].value) / len(result[0].value[0].value)
print(f"Average write IOPS for the last 60 seconds for {vm_name}: {average_write_iops}")
return average_write_iops
else:
return None
def get_average_write_latency(self, vm):
try:
content = self.__conn__.RetrieveContent()
except:
self.log.debug("Could not get content from vCenter when trying to get VM stats")
# Find the virtual machine by name
vm_name = str(vm)
vm = None
for obj in content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True).view:
if obj.name == vm_name:
vm = obj
break
if vm is None:
self.log.debug(f"Virtual machine '{vm_name}' not found")
return None
# Get performance manager
perf_manager = content.perfManager
# Define the metric ID for write latency (counterId = X) - replace X with the correct counter ID
# You'll need to find the specific counter ID for write latency in your vSphere environment.
# The counter for write latency may vary based on your configuration.
metric_id = vim.PerformanceManager.MetricId(counterId=10, instance="") # Replace X with the correct counter ID
end_time = datetime.datetime.now()
start_time = end_time - datetime.timedelta(seconds=60)
# Create a query specification for roll-up data
query_spec = vim.PerformanceManager.QuerySpec(
entity=vm,
metricId=[metric_id],
format="normal",
startTime=start_time,
endTime=end_time,
intervalId=20, # Use an appropriate interval for the roll-up data
)
# Query the performance statistics
result = perf_manager.QueryStats(querySpec=[query_spec])
if result:
# Get the average write latency for the last 60 seconds
if result[0].value[0].value:
average_write_latency = sum(result[0].value[0].value) / len(result[0].value[0].value)
self.log.info(f"Average write latency for the last 60 seconds for {vm_name}: {average_write_latency}")
return average_write_latency
return None
def disconnect(self):
if self.__conn__ == None:
self.log.debug(f"vCenter disconnect requested, but not currently connected.")
return
# Disconnect from vCenter
Disconnect(self.__conn__)
self.__conn__ = None
self.version = None
self.log.debug(f"Disconnected from vCenter")
+10
View File
@@ -0,0 +1,10 @@
# version.py
VERSION = "0.1.0"
def main():
# Put your main program code here
print(VERSION)
if __name__ == '__main__':
# This code will be executed only when the file is run as the main program
main()
+4
View File
@@ -0,0 +1,4 @@
print("Initializing zvma10 package...")
#from .zvma import zvm
from .zvma import zvmsite
+26
View File
@@ -0,0 +1,26 @@
import requests
from requests import Request, Session
# Create a Request object
url = 'http://httpbin.org/post'
headers = {'Content-Type': 'application/json'}
data = {'key': 'value'}
req = Request('POST', url, data=data, headers=headers)
# Prepare the request
prepared_req = req.prepare()
# Print the prepared request details
print("Prepared Request:")
print(f"URL: {prepared_req.url}")
print(f"Method: {prepared_req.method}")
print(f"Headers: {prepared_req.headers}")
print(f"Body: {prepared_req.body}")
# Send the request using a Session
with Session() as s:
response = s.send(prepared_req)
# Print the response
print(f"\nResponse Status Code: {response.status_code}")
print(response.text)
+10
View File
@@ -0,0 +1,10 @@
# version.py
VERSION = "0.1.0"
def main():
# Put your main program code here
print(VERSION)
if __name__ == '__main__':
# This code will be executed only when the file is run as the main program
main()
+1202
View File
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from requests.structures import CaseInsensitiveDict
from tinydb import TinyDB, Query
from tinydbstorage.storage import MemoryStorage
from logging.handlers import RotatingFileHandler
# Function to get VM Encryption Data from ZVMa version 9.7
def GetStatsFunc():
tempdb = TinyDB(storage=MemoryStorage) # ('./db.json') used for storing db on disk for debugging
dbvm = Query()
dbvpg = Query()
while (True) :
global token
global siteId
global siteName
if (token != ""):
log.info("Got Auth Token!")
log.debug("token: " + str(token))
log.debug("Stats Collector Loop Running")
metricsDictionary = {}
h2 = CaseInsensitiveDict()
h2["Accept"] = "application/json"
h2["Authorization"] = "Bearer " + token
## Statistics API
uri = "https://" + zvm_url + ":" + zvm_port + "/v1/statistics/vms/"
statsapi = requests.get(url=uri, timeout=3, headers=h2, verify=verifySSL)
statsapi_json = statsapi.json()
#log.debug(statsapi_json)
for vm in statsapi_json:
oldvmdata = dict()
CurrentIops = 0
CurrentWriteCounterInMBs = 0
CurrentSyncCounterInMBs = 0
CurrentNetworkTrafficCounterInMBs = 0
CurrentEncryptedLBs = 0
CurrentUnencryptedLBs = 0
CurrentTotalLBs = 0
CurrentPercentEncrypted = 0
VMName = "NA"
oldvmdata = tempdb.search(dbvm.VmIdentifier == vm['VmIdentifier'] and dbvpg.VpgIdentifier == vm['VpgIdentifier'])
log.info("Checking TempDB for VM " + vm['VmIdentifier'] + " in VPG " + vm['VpgIdentifier'])
if (oldvmdata):
log.info(vm['VmIdentifier'] + " Record Found, Updating DB")
log.debug(oldvmdata[0])
log.debug(tempdb.update(vm, dbvm.VmIdentifier == vm['VmIdentifier'] and dbvpg.VpgIdentifier == vm['VpgIdentifier']))
log.debug("!@!@!@!@!@ Stats !@!@!@!@!@")
VMName = oldvmdata[0]['VmName']
log.debug("Current VM " + str(VMName))
CurrentIops = abs(vm['IoOperationsCounter'] - oldvmdata[0]['IoOperationsCounter'])
log.debug("CurrentIops " + str(CurrentIops))
CurrentSyncCounterInMBs = abs(vm['SyncCounterInMBs'] - oldvmdata[0]['SyncCounterInMBs'])
log.debug("CurrentSyncCounterInMBs " + str(CurrentSyncCounterInMBs))
CurrentNetworkTrafficCounterInMBs = abs(vm['NetworkTrafficCounterInMBs'] - oldvmdata[0]['NetworkTrafficCounterInMBs'])
log.debug("CurrentNetworkTrafficCounterInMBs " + str(CurrentNetworkTrafficCounterInMBs))
CurrentEncryptedLBs = abs(vm['EncryptionStatistics']['EncryptedDataInLBs'] - oldvmdata[0]['EncryptionStatistics']['EncryptedDataInLBs'])
log.debug("CurrentEncryptedLBs " + str(CurrentEncryptedLBs))
CurrentUnencryptedLBs = abs(vm['EncryptionStatistics']['UnencryptedDataInLBs'] - oldvmdata[0]['EncryptionStatistics']['UnencryptedDataInLBs'])
log.debug("CurrentUnencryptedLBs " + str(CurrentUnencryptedLBs))
CurrentTotalLBs = abs(CurrentEncryptedLBs + CurrentUnencryptedLBs)
log.debug("CurrentTotalLBs " + str(CurrentTotalLBs))
if CurrentTotalLBs != 0:
CurrentPercentEncrypted = ((CurrentEncryptedLBs / CurrentTotalLBs) * 100)
else:
CurrentPercentEncrypted = 0
log.debug("CurrentPercentEncrypted " + str(CurrentPercentEncrypted))
else:
log.info(vm['VmIdentifier'] + " No Record Found, Inserting into DB")
#insert original VM record to tempdb
log.debug(tempdb.insert(vm))
# update database with VM name, for easier display in Grafana Legends
uri = "https://" + zvm_url + ":" + zvm_port + "/v1/vms/" + vm['VmIdentifier'] +"?vpgIdentifier=" + vm['VpgIdentifier']
try:
vapi = requests.get(url=uri, timeout=3, headers=h2, verify=verifySSL)
vapi_json = vapi.json()
except Exception as e:
log.error("Error while sending api request: " + str(e))
VMName = "Unknown"
else:
log.debug("vapi_json: " + str(vapi_json))
tempdb.update({'VmName': vapi_json['VmName']}, dbvm.VmIdentifier == vm['VmIdentifier'])
log.info("Added vm to tempdb " + vm['VmIdentifier'] + " - " + vapi_json['VmName'])
VMName = vapi_json['VmName']
# Store Calculated Metrics
metricsDictionary["vm_IoOperationsCounter{VpgIdentifier=\"" + str(vm['VpgIdentifier']) + "\",VmIdentifier=\"" + str(vm['VmIdentifier']) + "\",VmName=\"" + str(VMName) + "\",SiteIdentifier=\"" + str(siteId) + "\",SiteName=\"" + str(siteName) + "\"}"] = CurrentIops
metricsDictionary["vm_WriteCounterInMBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentWriteCounterInMBs
metricsDictionary["vm_SyncCounterInMBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentSyncCounterInMBs
metricsDictionary["vm_NetworkTrafficCounterInMBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentNetworkTrafficCounterInMBs
metricsDictionary["vm_EncryptedDataInLBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentEncryptedLBs
metricsDictionary["vm_UnencryptedDataInLBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentUnencryptedLBs
metricsDictionary["vm_TotalDataInLBs{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentTotalLBs
metricsDictionary["vm_PercentEncrypted{VpgIdentifier=\"" + vm['VpgIdentifier'] + "\",VmIdentifier=\"" + vm['VmIdentifier'] + "\",VmName=\"" + VMName + "\",SiteIdentifier=\"" + siteId + "\",SiteName=\"" + siteName + "\"}"] = CurrentPercentEncrypted
## Write metrics to a human readable metrics.txt file as well as a metrics file that is easy to get in prometheus
file_object = open('statsmetrics', 'w')
txt_object = open('statsmetrics.txt', 'w')
for item in metricsDictionary :
file_object.write(item)
file_object.write(" ")
file_object.write(str(metricsDictionary[item]))
file_object.write("\n")
txt_object.write(item)
txt_object.write(" ")
txt_object.write(str(metricsDictionary[item]))
txt_object.write("\n")
file_object.close()
txt_object.close()
log.debug("Starting Sleep for " + str(scrape_speed) + " seconds")
sleep(scrape_speed)
else:
log.debug("Waiting 1 second for Auth Token")
sleep(1)
+3
View File
@@ -0,0 +1,3 @@
print("Initializing zvma9_7 package...")
from .GetStatsFunc import GetStatsFunc
+4 -5
View File
@@ -1,9 +1,8 @@
version: "3.3"
services:
custom-exporter-in-python:
zerto-exporter:
build: .
command: python python-node-exporter.py
ports:
- "9999:9999"
environment:
@@ -11,10 +10,10 @@ services:
- ZVM_HOST=192.168.50.60
- ZVM_PORT=443
- CLIENT_ID=api-script
- CLIENT_SECRET=js51tDM8oappYUGRJBhF7bcsedNoHA5j
- CLIENT_SECRET=fcYMFuA5TkIUwp6b3hDUxim0f32z8erk
- LOGLEVEL=INFO #Valid settings are CRITICAL, ERROR, WARNING, INFO, DEBUG
- VCENTER_HOST=192.168.50.50
- VCENTER_USER=administrator@vsphere.local
- VCENTER_PASSWORD=password
- VCENTER_PASSWORD=Zertodata987!
volumes:
- "./app:/usr/src/app:rw"
- "./logs:/usr/src/app/logs/"