Initial Commit

This commit is contained in:
2023-09-08 19:05:37 +03:00
commit 7e60195cb7
185 changed files with 27107 additions and 0 deletions

165
api/.dockerignore Normal file
View File

@@ -0,0 +1,165 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Database
*.db
dashboard_database.db
# Ignore files
.dockerignore
.gitignore
# Docker
Dockerfile
docker-compose.yml

157
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,157 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Database
*.db
dashboard_database.db

31
api/Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
# syntax = docker/dockerfile:1
FROM python:3.11.5-slim as builder
RUN apt-get update && \
apt-get install -y libpq-dev gcc
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip3 install -r requirements.txt
FROM python:3.11.5-slim
# Set working dir
COPY --from=builder /opt/venv /opt/venv
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PATH="/opt/venv/bin:$PATH"
COPY . /usr/src/virt.dashboard
WORKDIR /usr/src/virt.dashboard/app
EXPOSE 5000
CMD [ "waitress-serve", "--port=5000", "wsgi:app" ]

0
api/app/__init__.py Normal file
View File

View File

@@ -0,0 +1,9 @@
{
"swagger": "2.0",
"info": {
"title": "Virt.Dashboard API",
"description": "Backend API for Virt.Dashboard",
"termsOfService": "",
"version": "1.1.2"
}
}

BIN
api/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

View File

@@ -0,0 +1,23 @@
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username_or_token, password):
if username_or_token == "user" and password == "passwd":
return True
return False
def post_login_required(func):
def post_decorator(*args, **kwargs):
print("post_decorator ", func, *args, **kwargs)
return auth.login_required(func)(*args, **kwargs)
if func.__name__ in ("post", "patch", "delete"):
return post_decorator
return func

View File

@@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

2
api/app/helpers/math.py Normal file
View File

@@ -0,0 +1,2 @@
def safe_division(n: int, d: int, r: int = 2):
return round((n / d), r) if d else 0

35
api/app/main.py Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/python
import logging
from flask import Flask
from flask_cors import CORS
from helpers.database import db
from swagger import create_api
from settings import config
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
datefmt="%m-%d %H:%M",
handlers=[logging.FileHandler(config.DASHBOARD_LOG), logging.StreamHandler()], # noqa
)
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) # noqa
app.config.update(
SQLALCHEMY_DATABASE_URI=config.SQLALCHEMY_DATABASE_URI,
DEBUG=config.DEBUG
)
db.init_app(app)
with app.app_context():
db.create_all()
create_api(
app=app,
port="5000",
host="localhost",
api_prefix=config.API_PREFIX_STRING,
custom_swagger=config.CUSTOM_SWAGGER
)

View File

View File

@@ -0,0 +1,9 @@
from helpers.database import db
from safrs import SAFRSBase
from safrs.api_methods import search
class BaseModel(SAFRSBase, db.Model):
__abstract__ = True
SAFRSBase.search = search

View File

View File

@@ -0,0 +1,21 @@
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class contour(BaseModel):
"""
description: Technological infrastructure contours.
"""
__tablename__ = "tcc_contours"
_s_collection_name = "contours"
_s_class_name = "contours"
exclude_rels = ["vcenter", "pcentral"]
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(64), unique=True, index=True, nullable=False)
description = Column(String(255))
vcenter = relationship("vcenter", back_populates="contour")
pcentral = relationship("pcentral", back_populates="contour")

View File

@@ -0,0 +1,27 @@
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class environment(BaseModel):
"""
description: Technological infrastructure environments.
"""
__tablename__ = "tcc_environments"
_s_collection_name = "environments"
_s_class_name = "environment"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(32), unique=True, index=True, nullable=False)
color = Column(String(7), index=False, nullable=True)
cluster = relationship("cluster", back_populates="environment")
pelement = relationship("pelement", back_populates="environment")
def to_dict(self):
result = BaseModel.to_dict(self)
result['name'] = result['name'].upper()
return result

View File

@@ -0,0 +1,21 @@
from datetime import datetime
from sqlalchemy import Column, String, Integer, DateTime
from models.common.base import BaseModel
class timestamp(BaseModel):
"""
description: Timestamps.
"""
__tablename__ = "tcc_timestamps"
_s_collection_name = "timestamps"
_s_class_name = "timestamps"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(64), unique=True, nullable=False)
timestamp = Column(
DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow
)

View File

View File

View File

@@ -0,0 +1,40 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class pcentral(BaseModel):
"""
description: Nutanix prism central model
"""
__tablename__ = "tnc_pcentrals"
_s_collection_name = "pcentrals"
_s_class_name = "pcentral"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(32), unique=True, index=True, nullable=False)
hostname = Column(String(64), unique=True, index=True, nullable=True)
ncc_version = Column(String(16), index=True, nullable=False)
aos_version = Column(String(16), index=True, nullable=False)
contour_id = Column(Integer, ForeignKey("tcc_contours.id"))
contour = relationship("contour", back_populates="pcentral")
npcreport = relationship(
"npcreport",
backref="pcentral",
cascade="save-update, delete",
lazy="dynamic"
)
pelement = relationship(
"pelement",
backref="pcentral",
cascade="save-update, delete",
lazy="dynamic"
)

View File

@@ -0,0 +1,52 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class pelement(BaseModel):
"""
description: Nutanix prism element model
"""
__tablename__ = "tnc_pelement"
_s_collection_name = "pelements"
_s_class_name = "pelement"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
ip = Column(String(15), unique=True, nullable=False)
name = Column(String(32), unique=True, nullable=False)
hostname = Column(String(64), unique=True, nullable=True)
description = Column(String(64), unique=False, nullable=False)
ncc_version = Column(String(16), index=True, nullable=False)
aos_version = Column(String(16), index=True, nullable=False)
cpu_decomiss = Column(Integer, nullable=True)
pcentral_id = Column(Integer, ForeignKey("tnc_pcentrals.id"))
environment_id = Column(Integer, ForeignKey("tcc_environments.id"))
environment = relationship("environment", back_populates="pelement")
pelement = relationship(
"npereport",
backref="pelement",
cascade="save-update, delete",
lazy="dynamic"
)
maintenance = relationship(
"nmreport",
backref="pelement",
cascade="save-update, delete",
lazy="dynamic"
)
def to_dict(self):
result = BaseModel.to_dict(self)
if (result['hostname'] is None):
result['hostname'] = result['hostname']
return result

View File

@@ -0,0 +1,37 @@
from datetime import datetime
from sqlalchemy import (
Column,
String,
Integer,
DateTime,
ForeignKey
)
from models.common.base import BaseModel
class nmreport(BaseModel):
"""
description: Nutanix hosts in maintenance mode report
"""
__tablename__ = "tnrm_report"
_s_collection_name = "nmreport"
_s_class_name = "nmreport"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
ip = Column(String(15), unique=True, nullable=False)
ipmi = Column(String(15), unique=True, nullable=False)
hypervisor_name = Column(String(64), unique=True, nullable=False)
hypervisor_type = Column(String(32), index=True)
hypervisor_state = Column(String(64), index=True)
serial = Column(String(32), unique=True, nullable=False)
reason = Column(String(255), nullable=True)
date = Column(DateTime, default=datetime.utcnow)
pelement_id = Column(
Integer,
ForeignKey("tnc_pelement.id"),
nullable=False,
index=True
)

View File

@@ -0,0 +1,34 @@
from sqlalchemy import (
Column,
Integer,
ForeignKey
)
from models.common.base import BaseModel
class npcreport(BaseModel):
"""
description: Nutanix prism central report model
"""
__tablename__ = "tnrpc_report"
_s_collection_name = "npcreport"
_s_class_name = "prismCentral"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
vms_total = Column(Integer, nullable=False)
cert_status = Column(Integer, nullable=False)
aos_status = Column(Integer, nullable=False)
projects_total = Column(Integer, nullable=False)
alerts_crit_ack = Column(Integer, nullable=False)
alerts_crit_nack = Column(Integer, nullable=False)
alerts_warn_ack = Column(Integer, nullable=False)
alerts_warn_nack = Column(Integer, nullable=False)
pcentral_id = Column(
Integer,
ForeignKey("tnc_pcentrals.id"),
nullable=False,
index=True
)

View File

@@ -0,0 +1,49 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class npereport(BaseModel):
"""
description: Nutanix prism element report model
"""
__tablename__ = "tnrpe_report"
_s_collection_name = "npereport"
_s_class_name = "prismElement"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
vms_total = Column(Integer, nullable=False)
cert_status = Column(Integer, nullable=False)
aos_status = Column(Integer, nullable=False)
storage_dedup = Column(Integer, nullable=False)
hosts_total = Column(Integer, nullable=False)
hosts_in_maintenance = Column(Integer, nullable=False)
storage_usage = Column(Integer, nullable=False)
cpu_usage = Column(Integer, nullable=False)
mem_usage = Column(Integer, nullable=False)
hypervisor = Column(String(32), nullable=True)
hypervisor_version = Column(String(32), nullable=True)
alerts_crit_ack = Column(Integer, nullable=False)
alerts_crit_nack = Column(Integer, nullable=False)
alerts_warn_ack = Column(Integer, nullable=False)
alerts_warn_nack = Column(Integer, nullable=False)
redundancy_factor = Column(Integer, nullable=False)
pelement_id = Column(
Integer,
ForeignKey("tnc_pelement.id"),
nullable=False,
index=True
)
decomission = relationship(
"nureport",
backref="npereport",
cascade="save-update, delete",
lazy="dynamic"
)

View File

@@ -0,0 +1,31 @@
from sqlalchemy import (
Column,
Integer,
Float,
ForeignKey
)
from models.common.base import BaseModel
class nureport(BaseModel):
"""
description: Nutanix utilization report
"""
__tablename__ = "tnru_report"
_s_collection_name = "nureport"
_s_class_name = "utilization"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
cpu_avg = Column(Float, nullable=False)
cpu_peak = Column(Float, nullable=False)
mem_avg = Column(Float, nullable=False)
mem_peak = Column(Float, nullable=False)
storage = Column(Float, nullable=False)
pelement_id = Column(
Integer,
ForeignKey("tnrpe_report.id"),
nullable=False,
index=True
)

View File

View File

View File

@@ -0,0 +1,54 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class cluster(BaseModel):
"""
description: VMware cluster model
"""
__tablename__ = "tvc_clusters"
_s_collection_name = "clusters"
exclude_rels = ["capacity", "datastores", "sharedNetwork"]
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(64), unique=True, index=True, nullable=False)
vcenter_id = Column(Integer, ForeignKey("tvc_vcenters.id"), nullable=False)
environment_id = Column(
Integer,
ForeignKey("tcc_environments.id"),
nullable=True
)
vcenter = relationship("vcenter", back_populates="cluster")
environment = relationship("environment", back_populates="cluster")
capacity = relationship(
"capacity",
backref="cluster",
cascade="save-update, delete",
lazy="dynamic"
)
maintenance = relationship(
"maintenance",
backref="cluster",
cascade="save-update, delete",
lazy="dynamic"
)
datastores = relationship(
"datastore",
backref="cluster",
cascade="save-update, delete",
lazy="dynamic"
)
sharedNetwork = relationship(
"sharedNetwork",
backref="cluster",
cascade="save-update, delete",
lazy="dynamic"
)

View File

@@ -0,0 +1,36 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class vcenter(BaseModel):
"""
description: VMware vcenter model
"""
__tablename__ = "tvc_vcenters"
_s_collection_name = "vcenters"
exclude_rels = ["rvm_vcenter"]
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
hostname = Column(String(64), unique=True, index=True, nullable=False)
contour_id = Column(Integer, ForeignKey("tcc_contours.id"))
cluster = relationship(
"cluster",
back_populates="vcenter",
cascade="save-update, delete"
)
contour = relationship("contour", back_populates="vcenter")
vcenter = relationship(
"maintenance",
backref="vcenter",
cascade="save-update, delete"
)

View File

@@ -0,0 +1,25 @@
from datetime import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.orm import relationship
from models.common.base import BaseModel
class frame(BaseModel):
__tablename__ = "tvrc_frames"
_s_collection_name = "frames"
http_methods = {"get", "post", "delete"}
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
timestamp = Column(
DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow
)
capacity = relationship(
"capacity",
backref="frame",
cascade="save-update, delete"
)

View File

@@ -0,0 +1,131 @@
from safrs import jsonapi_rpc, SAFRSFormattedResponse
from sqlalchemy import (
Column,
Integer,
Float,
ForeignKey
)
from helpers.database import db
from helpers.math import safe_division
from models.common.base import BaseModel
from models.vmware.reports.capacity.frame import frame
class capacity(BaseModel):
"""
description: Capacity report model
"""
__tablename__ = "tvrc_report"
_s_collection_name = "capacity"
http_methods = {"get", "post"}
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
hosts_on_duty = Column(Integer, nullable=False)
hosts_in_maintenance = Column(Integer, nullable=False)
hosts_reserved = Column(Integer, nullable=False)
hosts_staged_dcm = Column(Integer, nullable=False)
vms_total = Column(Integer, nullable=False)
sockets_total = Column(Integer, nullable=False)
pcpu_total = Column(Integer, nullable=False)
pcpu_host_max = Column(Integer, nullable=False)
vcpu_provisioned = Column(Integer, nullable=False)
pmemory_total = Column(Float, nullable=False)
vmemory_provisioned = Column(Float, nullable=False)
pstorage_total = Column(Float, nullable=False)
vstorage_used = Column(Float, nullable=False)
vstorage_provisioned = Column(Float, nullable=False)
rdm_total = Column(Float, nullable=False)
pcpu_ovp_target = Column(Float, nullable=False)
pmemory_ovp_target = Column(Float, nullable=False)
pstorage_ovp_target = Column(Float, nullable=False)
pcpu_retire_target = Column(Float, nullable=False)
frame_id = Column(
Integer, ForeignKey("tvrc_frames.id"),
nullable=False,
index=True
)
cluster_id = Column(
Integer,
ForeignKey("tvc_clusters.id"),
nullable=False,
index=True
)
@classmethod
@jsonapi_rpc(http_methods=["GET"])
def get_last(self, *args, **kwargs):
"""
description : Get capacity report by last frameid
summary : Get last capacity report
tags:
- capacity
produces:
- application/xml
- application/json
"""
frame_obj = db.session.query(frame).order_by(frame.id.desc()).first()
reports = db.session.query(capacity).\
filter(capacity.frame_id == frame_obj.id).all()
data = [reports]
response = SAFRSFormattedResponse(
data,
self._s_meta(),
{},
{},
len(reports)
)
return response
def __init__(self, *args, **kwargs):
pcpu_ovp_current = kwargs.pop("pcpu_ovp_current", None) # noqa
pmemory_ovp_current = kwargs.pop("pmemory_ovp_current", None) # noqa
pstorage_ovp_current = kwargs.pop("pstorage_ovp_current", None) # noqa
cpu_cap_used = kwargs.pop("cpu_cap_used", None) # noqa
mem_cap_used = kwargs.pop("mem_cap_used", None) # noqa
str_cap_used = kwargs.pop("str_cap_used", None) # noqa
hosts_total = kwargs.pop("hosts_total", None) # noqa
BaseModel.__init__(self, **kwargs)
def to_dict(self):
result = BaseModel.to_dict(self)
result['pcpu_ovp_current'] = safe_division(
result['vcpu_provisioned'],
result['pcpu_total']
)
result['pmemory_ovp_current'] = safe_division(
result['vmemory_provisioned'],
result['pmemory_total']
)
result['pstorage_ovp_current'] = safe_division(
result['vstorage_provisioned'],
result['pstorage_total']
)
result['cpu_cap_used'] = round(safe_division(
result['pcpu_ovp_current'],
result['pcpu_ovp_target']
)*100)
result['mem_cap_used'] = round(safe_division(
result['pmemory_ovp_current'],
result['pmemory_ovp_target']
)*100)
result['str_cap_used'] = round(safe_division(
result['pstorage_ovp_current'],
result['pstorage_ovp_target']
)*100)
result['hosts_total'] = result['hosts_on_duty'] +\
result['hosts_reserved'] +\
result['hosts_in_maintenance']
return result

View File

@@ -0,0 +1,56 @@
from sqlalchemy import (
Column,
String,
Integer,
ForeignKey
)
from helpers.math import safe_division
from models.common.base import BaseModel
class datastore(BaseModel):
"""
description: Datastores report model
"""
__tablename__ = "tvrd_report"
_s_collection_name = "datastores"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(64), index=True, nullable=False)
space_used = Column(Integer, nullable=False)
space_total = Column(Integer, nullable=False)
space_provisioned = Column(Integer, nullable=False)
cluster_id = Column(
Integer,
ForeignKey("tvc_clusters.id"),
nullable=False,
index=True
)
def __init__(self, *args, **kwargs):
ovp_current = kwargs.pop("ovp_current", None) # noqa
space_free_percent = kwargs.pop("space_free_percent", None) # noqa
cap_used = kwargs.pop("cap_used", None) # noqa
BaseModel.__init__(self, **kwargs)
def to_dict(self):
result = BaseModel.to_dict(self)
result["ovp_current"] = safe_division(
result["space_provisioned"],
result["space_total"]
)
result["space_free_percent"] = round((1 - safe_division(
result["space_used"],
result["space_total"]
))*100, 0)
result['cap_used'] = round(safe_division(
result['ovp_current'],
2
)*100)
return result

View File

@@ -0,0 +1,38 @@
from sqlalchemy import (
Column,
String,
Integer,
DateTime,
ForeignKey
)
from models.common.base import BaseModel
class maintenance(BaseModel):
__tablename__ = "tvrm_report"
_s_collection_name = "maintenance"
http_methods = {"get", "post", "delete"}
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
folder = Column(String(255), nullable=False)
hostname = Column(String(64), unique=True, nullable=False)
state = Column(String(64), index=True)
reason = Column(String(255))
placedby = Column(String(64))
placedbyFN = Column(String(64))
date = Column(DateTime)
vcenter_id = Column(
Integer,
ForeignKey("tvc_vcenters.id"),
nullable=False,
index=True
)
cluster_id = Column(
Integer,
ForeignKey("tvc_clusters.id"),
nullable=True,
index=True
)

View File

@@ -0,0 +1,49 @@
from sqlalchemy import (
Column,
String,
Integer,
Float,
ForeignKey
)
from models.common.base import BaseModel
class sharedNetwork(BaseModel):
"""
description: Shared Networks report model
"""
__tablename__ = "tvrsn_report"
_s_collection_name = "sharedNetworks"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
vrf = Column(String(64), nullable=False)
vrfId = Column(Integer, nullable=False, index=True)
subnet = Column(String(15), nullable=False)
subnetId = Column(Integer, nullable=False, unique=True, index=True)
subnetMask = Column(Integer, nullable=False)
requestsID = Column(String(32))
bussinessLine = Column(String(32))
subnetManager = Column(String(32), index=True)
virtSubnetName = Column(String(32))
virtSubnetUUID = Column(String(32))
freeIPPercent = Column(Float, nullable=False)
cluster_id = Column(
Integer,
ForeignKey("tvc_clusters.id"),
nullable=True,
index=True
)
def __init__(self, *args, **kwargs):
platform = kwargs.pop("platform", None) # noqa
BaseModel.__init__(self, **kwargs)
def to_dict(self):
result = BaseModel.to_dict(self)
result["platform"] = "vmware"
return result

46
api/app/settings.py Normal file
View File

@@ -0,0 +1,46 @@
import json
from functools import lru_cache
from dotenv import load_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
ENVIRONMENT: str = "dev"
SWAGGER_HOST: str = "localhost:5000"
API_PREFIX_STRING: str = "/api"
API_DESCRIPTION: str = "vtb_dashboard_api"
DASHBOARD_LOG: str = "vtb_dashboard_api.log"
POSTGRES_SERVER: str = "localhost"
POSTGRES_PORT: int = 5432
POSTGRES_USER: str
POSTGRES_PASSWORD: str
POSTGRES_DB: str = "virt.dashboard"
DEBUG: bool = False
@property
def SQLALCHEMY_DATABASE_URI(self):
return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_SERVER}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}" # noqa
@property
def CUSTOM_SWAGGER(self):
with open("custom_swagger.json") as j_fp:
custom_swagger = json.load(j_fp)
custom_swagger["host"] = self.SWAGGER_HOST
return custom_swagger
model_config = SettingsConfigDict(extra='allow', env_file=".env")
@lru_cache
def get_config():
load_dotenv()
return Settings()
config = get_config()

59
api/app/swagger.py Normal file
View File

@@ -0,0 +1,59 @@
import logging
from flask import Flask
from safrs import SAFRSAPI
from models.common.core.contour import contour as mcc_Contour
from models.common.core.environment import environment as mcc_Environment
from models.common.core.timestamps import timestamp as mcc_Timestamp
from models.vmware.core.vcenter import vcenter as mvc_vCenter
from models.vmware.core.cluster import cluster as mvc_Cluster
from models.vmware.reports.capacity.frame import frame as mvrc_Frame
from models.vmware.reports.capacity.report import capacity as mvrc_Report
from models.vmware.reports.mmhosts.report import maintenance as mvrm_Report
from models.vmware.reports.datastores.report import datastore as mvrd_Report # noqa
from models.vmware.reports.sharedNetworks.report import sharedNetwork as mvrsn_Report # noqa
from models.nutanix.core.pelement import pelement as mnc_Cluster
from models.nutanix.core.pcentral import pcentral as mnc_Central
from models.nutanix.reports.prismElement.report import npereport as mnrpe_Report # noqa
from models.nutanix.reports.prismCentral.report import npcreport as mnrpc_Report # noqa
from models.nutanix.reports.mmhosts.report import nmreport as mnrm_Report
from models.nutanix.reports.utilization.report import nureport as mnru_Report # noqa
logger = logging.getLogger(__name__)
def create_api(
app: Flask,
host: str,
port: str,
api_prefix: str,
custom_swagger: dict = {}
) -> None:
api = SAFRSAPI(
app,
host=host,
schemes=["http", "https"],
port=port,
prefix=api_prefix,
custom_swagger=custom_swagger
)
for model in [mcc_Contour, mcc_Environment, mcc_Timestamp]:
api.expose_object(model, url_prefix="/common")
for model in [mvc_vCenter, mvc_Cluster]:
api.expose_object(model, url_prefix="/vmware")
for model in [mvrc_Report, mvrm_Report, mvrd_Report, mvrsn_Report]:
api.expose_object(model, url_prefix="/vmware/report")
api.expose_object(mvrc_Frame, url_prefix="/vmware/report/capacity")
for model in [mnc_Cluster, mnc_Central]:
api.expose_object(model, url_prefix="/nutanix")
for model in [mnrpe_Report, mnrpc_Report, mnrm_Report, mnru_Report]:
api.expose_object(model, url_prefix="/nutanix/report")

5
api/app/wsgi.py Normal file
View File

@@ -0,0 +1,5 @@
from main import app
from settings import config
if __name__ == "__main__":
app.run(debug=config.DEBUG, host="0.0.0.0", port=5000)

31
api/requirements.txt Normal file
View File

@@ -0,0 +1,31 @@
aniso8601>=9.0.1
build>=0.10.0
click>=8.1.3
Flask>=2.2.5
Flask-Cors>=3.0.10
Flask-RESTful>=0.3.9
flask-restful-swagger-2>=0.35
Flask-SQLAlchemy>=3.0.3
flask-swagger-ui>=4.11.1
Flask-HTTPAuth>=4.8.0
greenlet>=2.0.2
importlib-metadata>=6.0.0
itsdangerous>=2.1.2
Jinja2>=3.1.2
MarkupSafe>=2.1.2
packaging>=23.0
pyproject-hooks>=1.0.0
pytz>=2022.7.1
PyYAML>=6.0
six>=1.16.0
SQLAlchemy>=2.0.3
tomli>=2.0.1
typing-extensions>=4.4.0
Werkzeug>=2.2.2
zipp>=3.13.0
safrs>=3.0.4
psycopg2-binary>=2.9.7
pydantic>=2.3.0
pydantic-settings>=2.0.3
python-dotenv>=1.0.0
waitress>=2.1.2