Cyclops 4 HPC is the purpose built stack to support large HPC centers with resource accounting and billing of cluster as well as cloud resources.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

682 lines
19 KiB

package dbManager
import (
"errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/Cyclops-Labs/cyclops-4-hpc.git/services/customerdb/models"
l "gitlab.com/cyclops-utilities/logging"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const (
statusDuplicated = iota
statusFail
statusMissing
statusOK
)
// DbParameter is the struct defined to group and contain all the methods
// that interact with the database.
// On it there is the following parameters:
// - connStr: strings with the connection information to the database
// - Db: a gorm.DB pointer to the db to invoke all the db methods
type DbParameter struct {
connStr string
Db *gorm.DB
Metrics map[string]*prometheus.GaugeVec
}
// New is the function to create the struct DbParameter.
// Parameters:
// - dbConn: strings with the connection information to the database
// - tables: array of interfaces that will contains the models to migrate
// to the database on initialization
// Returns:
// - DbParameter: struct to interact with dbManager functionalities
func New(dbConn string, tables ...interface{}) *DbParameter {
l.Trace.Printf("[DB] Gerenating new DBParameter.\n")
var (
dp DbParameter
err error
)
dp.connStr = dbConn
dp.Db, err = gorm.Open(postgres.Open(dbConn), &gorm.Config{})
if err != nil {
l.Error.Printf("[DB] Error opening connection. Error: %v\n", err)
panic(err)
}
l.Trace.Printf("[DB] Migrating tables.\n")
//Database migration, it handles everything
dp.Db.AutoMigrate(tables...)
//l.Trace.Printf("[DB] Generating hypertables.\n")
// Hypertables creation for timescaledb in case of needed
//dp.Db.Exec("SELECT create_hypertable('" + dp.Db.NewScope(&models.TABLE).TableName() + "', 'TIMESCALE-ROW-INDEX');")
return &dp
}
// ListResellers function extracts from the db all the resellers in the system.
// It only list the resellers, not their linked customers neither the linked
// products to each customer.
// Returns:
// - r: slice of the reseller model with all the resellers in the system.
func (d *DbParameter) ListResellers() (r []*models.Reseller, e error) {
l.Trace.Printf("[DB] Attempting to fetch reseller list now.\n")
if e = d.Db.Find(&r).Error; e != nil {
l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e)
}
l.Trace.Printf("[DB] Found [ %d ] resellers in the db.\n", len(r))
return
}
// ListCustomers function extracts from the db all the customers in the system.
// It only list the customerss, not their linked products.
// Returns:
// - c: slice of the customers model with all the customers in the system.
func (d *DbParameter) ListCustomers() (c []*models.Customer, e error) {
l.Trace.Printf("[DB] Attempting to fetch customers list now.\n")
if e = d.Db.Find(&c).Error; e != nil {
l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e)
}
l.Trace.Printf("[DB] Found [ %d ] customers in the db.\n", len(c))
return
}
// ListProducts function extracts from the db all the products in the system.
// Returns:
// - p: slice of the products model with all the products in the system.
func (d *DbParameter) ListProducts() (p []*models.Product, e error) {
l.Trace.Printf("[DB] Attempting to fetch products list now.\n")
if e = d.Db.Find(&p).Error; e != nil {
l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e)
}
l.Trace.Printf("[DB] Found [ %d ] products in the db.\n", len(p))
return
}
// GetReseller function extracts from the db the information that the system
// has about the specified reseller.
// Internally this function makes use of GetCustomers() to fill the information
// about all the customers linked to the specified reseller.
// Parameters:
// - id: string containing the id of the reseller to be gotten.
// - host: string containing the basepath of the server for filling out the APILink
// field of the linked customers and products.
// Returns:
// - A reference to the reseller's model containing all the information in the
// system linked to the specified reseller.
func (d *DbParameter) GetReseller(id string, host string) (*models.Reseller, error) {
l.Trace.Printf("[DB] Attempting to get reseller [ %v ] now.\n", id)
var r models.Reseller
e := d.Db.Where(&models.Reseller{ResellerID: id}).First(&r).Error
if errors.Is(e, gorm.ErrRecordNotFound) {
l.Warning.Printf("[DB] Unable to fetch existing record for reseller [ %v ], check with administrator.\n", id)
} else {
l.Info.Printf("[DB] Found existing record for reseller [ %v ] successfully.\n", r.Name)
r.Customers = d.GetCustomers(id, host)
}
return &r, e
}
// GetCustomer function extracts from the db the information that the system
// has about the specified customer.
// Internally this function makes use of GetProducts() to fill the information
// about all the products linked to the specified customer.
// Parameters:
// - id: string containing the id of the customer to be gotten.
// - host: string containing the basepath of the server for filling out the APILink
// field of the linked products.
// Returns:
// - A reference to the customer's model containing all the information in the
// system linked to the specified customer.
func (d *DbParameter) GetCustomer(id string, host string) (*models.Customer, error) {
l.Trace.Printf("[DB] Attempting to get customer [ %v ] now.\n", id)
var c models.Customer
e := d.Db.Where(&models.Customer{CustomerID: id}).First(&c).Error
if errors.Is(e, gorm.ErrRecordNotFound) {
l.Warning.Printf("[DB] Unable to fetch existing record for customer [ %v ], check with administrator.\n", id)
} else {
l.Info.Printf("[DB] Found existing record for customer [ %v ] successfully.\n", c.Name)
c.Products = d.GetProducts(id, host)
}
return &c, e
}
// GetProduct function extracts from the db the information that the system
// has about the specified product.
// Parameters:
// - id: string containing the id of the product to be gotten.
// Returns:
// - A reference to the product's model containing all the information in the
// system linked to the specified product.
func (d *DbParameter) GetProduct(id string) (*models.Product, error) {
l.Trace.Printf("[DB] Attempting to get product [ %v ] now.\n", id)
var p models.Product
e := d.Db.Where(&models.Product{ProductID: id}).First(&p).Error
if errors.Is(e, gorm.ErrRecordNotFound) {
l.Warning.Printf("[DB] Unable to fetch existing record for product [ %v ], check with administrator.\n", id)
} else {
l.Info.Printf("[DB] Found existing record for product [ %v ] successfully.\n", p.Name)
}
return &p, e
}
// GetCustomers function extracts from the db the information that the system
// has about all the customers linked to the specified reseller id.
// Internally this function makes use of GetProducts() to fill the information
// about all the products linked to each customer.
// Parameters:
// - id: string containing the id of the customer to be gotten.
// - host: string containing the basepath of the server for filling out the APILink
// field of the customers and the linked products.
// Returns:
// - A slice of the customer's model containing all the information in the
// system linked to the specified reseller id.
func (d *DbParameter) GetCustomers(id string, host string) (c []*models.Customer) {
l.Trace.Printf("[DB] Attempting to fetch customers for reseller [ %v ] now.\n", id)
var custs, customers []models.Customer
if e := d.Db.Where(&models.Customer{ResellerID: id}).Find(&customers).Error; e != nil {
l.Warning.Printf("[DB] Error in DB operation while retrieving linked custommers. Error: %v\n", e)
}
for _, customer := range customers {
customer.Products = d.GetProducts(customer.CustomerID, host)
customer.APILink = host + "/customer/" + customer.CustomerID
custs = append(custs, customer)
}
//This is the only way to avoid the issue with the unproper assigning of
//&Elemnt when using range over and array
for id := range custs {
c = append(c, &custs[id])
}
l.Trace.Printf("[DB] Found [ %d ] customers for reseller [ %v ].\n", len(c), id)
return
}
// GetProducts function extracts from the db the information that the system
// has about all the products linked to the specified customer id.
// Parameters:
// - id: string containing the id of the customer to be gotten.
// - host: string containing the basepath of the server for filling out the APILink
// field of products.
// Returns:
// - A slice of the product's model containing all the information in the
// system linked to the specified customer id.
func (d *DbParameter) GetProducts(id string, host string) (p []*models.Product) {
l.Trace.Printf("[DB] Attempting to fetch products for customer [ %v ] now.\n", id)
var products []models.Product
if e := d.Db.Where(&models.Product{CustomerID: id}).Find(&products).Error; e != nil {
l.Warning.Printf("[DB] Error in DB operation while retrieving linked products. Error: %v\n", e)
}
for i := range products {
product := products[i]
product.APILink = host + "/product/" + product.ProductID
p = append(p, &product)
}
l.Trace.Printf("[DB] Found [ %d ] products for customer [ %v ].\n", len(p), id)
return
}
// AddReseller function inserts the reseller and its linked customers and products
// into the system.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the reseller or any of its linked
// components in the db.
// Parameters:
// - r: reseller's model containing the information to be imported in the db.
// Returns:
// - rs: integer with the state of adding the reseller itself.
// - cs: integer with the state of adding the linked customers.
// - ps: integer with the state of adding the linked products.
func (d *DbParameter) AddReseller(r *models.Reseller) (id string, rs int, cs int, ps int) {
l.Trace.Printf("[DB] Attempting to add reseller [ %v ] now.\n", r.ResellerID)
var r0 models.Reseller
if e := d.Db.Where(r).First(&r0).Error; errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Create(r); e.Error == nil {
l.Info.Printf("[DB] Inserted new record for reseller [ %v ] successfully.\n", r.Name)
d.Metrics["count"].With(prometheus.Labels{"type": "Resellers added"}).Inc()
rs, cs, ps = statusOK, statusOK, statusOK
id = (*e.Statement.Model.(*models.Reseller)).ResellerID
for _, c := range r.Customers {
c.ResellerID = id
_, a, b := d.AddCustomer(c)
if a != statusOK {
cs = statusFail
}
if b != statusOK {
ps = statusFail
}
}
} else {
l.Warning.Printf("[DB] Unable to insert the record for reseller [ %v ], check with administrator.\n", r.Name)
rs = statusFail
}
} else {
l.Warning.Printf("[DB] Record for reseller [ %v ] already exists, check with administrator.\n", r.Name)
rs = statusDuplicated
}
return
}
// AddCustomer function inserts the customer and its linked products into the system.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the customer or any of its linked
// components in the db.
// Parameters:
// - c: customer's model containing the information to be imported in the db.
// Returns:
// - cs: integer with the state of adding the customer itself.
// - ps: integer with the state of adding the linked products.
func (d *DbParameter) AddCustomer(c *models.Customer) (id string, cs int, ps int) {
l.Trace.Printf("[DB] Attempting to add customer [ %v ] now.\n", c.CustomerID)
var c0 models.Customer
if e := d.Db.Where(c).First(&c0).Error; errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Create(c); e.Error == nil {
l.Info.Printf("[DB] Inserted new record for customer [ %v ] successfully.\n", c.Name)
d.Metrics["count"].With(prometheus.Labels{"type": "Customers added"}).Inc()
cs, ps = statusOK, statusOK
id = (*e.Statement.Model.(*models.Customer)).CustomerID
for _, p := range c.Products {
p.CustomerID = id
_, a := d.AddProduct(p)
if a != statusOK {
ps = statusFail
}
}
} else {
l.Warning.Printf("[DB] Unable to insert the record for customer [ %v ], check with administrator.\n", c.Name)
cs = statusFail
}
} else {
l.Warning.Printf("[DB] Record for customer [ %v ] already exists, check with administrator.\n", c.Name)
cs = statusDuplicated
}
return
}
// AddProduct function inserts the product into the system.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the product in the db.
// Parameters:
// - p: product's model containing the information to be imported in the db.
// Returns:
// - ps: integer with the state of adding the product.
func (d *DbParameter) AddProduct(p *models.Product) (id string, ps int) {
l.Trace.Printf("[DB] Attempting to add product [ %v ] now.\n", p.ProductID)
var p0 models.Product
if e := d.Db.Where(p).First(&p0).Error; errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Create(p); e.Error == nil {
l.Info.Printf("[DB] Inserted new record for prolduct [ %v ] successfully.\n", p.Name)
id = (*e.Statement.Model.(*models.Product)).ProductID
d.Metrics["count"].With(prometheus.Labels{"type": "Products added"}).Inc()
ps = statusOK
} else {
l.Warning.Printf("[DB] Unable to insert the record for product [ %v ], check with administrator.\n", p.Name)
ps = statusFail
}
} else {
l.Warning.Printf("[DB] Record for product [ %v ] already exists, check with administrator.\n", p.Name)
ps = statusDuplicated
}
return
}
// UpdateReseller function inserts the updated reseller information and its
// linked customers and products into the system, creating new ones in case of
// no previous version found in the database.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the updated reseller or any of its linked
// components in the db.
// Parameters:
// - r: reseller's model containing the information to be imported in the db.
// Returns:
// - rs: integer with the state of updating the reseller itself.
// - cs: integer with the state of updating the linked customers.
// - ps: integer with the state of updating the linked products.
func (d *DbParameter) UpdateReseller(r *models.Reseller) (rs int, cs int, ps int) {
l.Trace.Printf("[DB] Attempting to update reseller [ %v ] now.\n", r.ResellerID)
var r0 models.Reseller
if e := d.Db.Where(models.Reseller{ResellerID: r.ResellerID}).First(&r0).Error; !errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Model(&r0).Updates(*r).Error; e == nil {
l.Info.Printf("[DB] Updated record for reseller [ %v ] successfully.\n", r.Name)
d.Metrics["count"].With(prometheus.Labels{"type": "Resellers updated"}).Inc()
rs, cs, ps = statusOK, statusOK, statusOK
for _, c := range r.Customers {
c.ResellerID = r.ResellerID
if a, _ := d.UpdateCustomer(c); a != statusOK {
l.Trace.Printf("[DB] Attempt to update customer [ %v ] failed, considering it as new customer to be added.\n", c.CustomerID)
_, b, d := d.AddCustomer(c)
if b != statusOK {
l.Trace.Printf("[DB] Attempt to add customer [ %v ] failed.\n", c.CustomerID)
cs = statusFail
}
if d != statusOK {
ps = statusFail
}
}
}
} else {
l.Warning.Printf("[DB] Unable to update record for reseller [ %v ], check with administrator.\n", r.Name)
rs = statusFail
}
} else {
l.Warning.Printf("[DB] Record for reseller [ %v ] not found, check with administrator.\n", r.Name)
rs = statusMissing
}
return
}
// UpdateCustomer function inserts the updated customer information and its
// linked products into the system, creating new ones in case of no previous
// version found in the database.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the updated customer or any of its linked
// components in the db.
// Parameters:
// - c: customer's model containing the information to be imported in the db.
// Returns:
// - cs: integer with the state of updating the customer itself.
// - ps: integer with the state of updating the linked products.
func (d *DbParameter) UpdateCustomer(c *models.Customer) (cs int, ps int) {
l.Trace.Printf("[DB] Attempting to update customer [ %v ] now.\n", c.CustomerID)
var c0 models.Customer
if e := d.Db.Where(&models.Customer{CustomerID: c.CustomerID}).First(&c0).Error; !errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Model(&c0).Updates(*c).Error; e == nil {
l.Info.Printf("[DB] Updated record for customer [ %v ] successfully.\n", c.Name)
d.Metrics["count"].With(prometheus.Labels{"type": "Customers updated"}).Inc()
cs, ps = statusOK, statusOK
for _, p := range c.Products {
p.CustomerID = c.CustomerID
if d.UpdateProduct(p) != statusOK {
l.Trace.Printf("[DB] Attempt to update product [ %v ] failed, considering it as new product to be added.\n", p.ProductID)
_, a := d.AddProduct(p)
if a != statusOK {
l.Trace.Printf("[DB] Attempt to add product [ %v ] failed.\n", p.ProductID)
cs = statusFail
}
}
}
} else {
l.Warning.Printf("[DB] Unable to update record for customer [ %v ], check with administrator.\n", c.Name)
cs = statusFail
}
} else {
l.Warning.Printf("[DB] Record for customer [ %v ] not found, check with administrator.\n", c.Name)
cs = statusMissing
}
return
}
// UpdateProduct function inserts the updated product information in the system.
// Since the system is designed to not raised a problem when it fails to add new
// items in the db, the method constains a basic feedback control to know if
// there was a problem when inserting the updated product in the db.
// Parameters:
// - p: product's model containing the information to be imported in the db.
// Returns:
// - ps: integer with the state of updating the product.
func (d *DbParameter) UpdateProduct(p *models.Product) (ps int) {
l.Trace.Printf("[DB] Attempting to update product [ %v ] now.\n", p.ProductID)
var p0 models.Product
if e := d.Db.Where(&models.Product{ProductID: p.ProductID}).First(&p0).Error; !errors.Is(e, gorm.ErrRecordNotFound) {
if e := d.Db.Model(&p0).Updates(*p).Error; e == nil {
l.Info.Printf("[DB] Updated record for product [ %v ] successfully.\n", p.Name)
d.Metrics["count"].With(prometheus.Labels{"type": "Products updated"}).Inc()
ps = statusOK
} else {
l.Warning.Printf("[DB] Unable to update record for product [ %v ], check with administrator.\n", p.Name)
ps = statusFail
}
} else {
l.Warning.Printf("[DB] Record for product [ %v ] not found, check with administrator.\n", p.Name)
ps = statusMissing
}
return
}