mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-11 15:44:52 +08:00
308 lines
8.1 KiB
Go
308 lines
8.1 KiB
Go
package schema
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/gravitl/netmaker/db"
|
|
"github.com/gravitl/netmaker/servercfg"
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Node struct {
|
|
ID string `gorm:"primaryKey"`
|
|
OwnerID string
|
|
HostID string
|
|
// Ideally, a foreign key relationship between host and node
|
|
// must exist, but here we tell gorm to not create the
|
|
// constraint.
|
|
//
|
|
// 1. A host's lifecycle may differ from a node's lifecycle.
|
|
// So we don't want to cascade delete a node record when the
|
|
// host is deleted.
|
|
//
|
|
// 2. Since we don't allow updating a host's id, we don't
|
|
// need to cascade update the foreign key.
|
|
Host Host `gorm:"-"`
|
|
LocalAddress string
|
|
NetworkID string
|
|
// Network foreign key relationship is also ignored for the
|
|
// same reason as Host.
|
|
Network Network `gorm:"-"`
|
|
NetworkRange string
|
|
NetworkRange6 string
|
|
Address string
|
|
Address6 string
|
|
Server string
|
|
Connected bool
|
|
Action string
|
|
|
|
// GatewayNodeID is the ID of the node that this node uses as a
|
|
// Gateway. If nil, this node does not use any node as its
|
|
// Gateway.
|
|
GatewayNodeID *string
|
|
// GatewayNode is the node that this node uses as a Gateway.
|
|
// If nil, this node does not use any node as its Gateway.
|
|
GatewayNode *Node
|
|
// GatewayFor is the list of Nodes that use this node as a
|
|
// Gateway.
|
|
GatewayFor []Node `gorm:"foreignKey:GatewayNodeID"`
|
|
// GatewayNodeConfig is the Gateway configuration of this
|
|
// node. If nil, this node is not a Gateway node.
|
|
GatewayNodeConfig *datatypes.JSONType[GatewayNodeConfig] `gorm:"foreignKey:GatewayNodeConfigID"`
|
|
|
|
// EgressGatewayNodeConfig is the Egress Gateway configuration
|
|
// of this node. If nil, this node is not an Egress Gateway
|
|
// node.
|
|
EgressGatewayNodeConfig *datatypes.JSONType[EgressGatewayNodeConfig] `gorm:"foreignKey:EgressGatewayNodeConfigID"`
|
|
|
|
// FailOverPeers is the list of peer nodes that this node
|
|
// connects to using the network's FailOver.
|
|
FailOverPeers datatypes.JSONMap
|
|
|
|
// InternetGatewayNodeID is the ID of the node that this node
|
|
// uses as an Internet Gateway. If nil, this node does not use
|
|
// any node as its Internet Gateway.
|
|
InternetGatewayNodeID *string
|
|
// InternetGatewayNode is the node that this node uses as an
|
|
// Internet Gateway. If nil, this node does not use any node
|
|
// as its Internet Gateway.
|
|
InternetGatewayNode *Node `gorm:"foreignKey:InternetGatewayNodeID"`
|
|
// InternetGatewayFor is the list of nodes that use this node
|
|
// as a Gateway.
|
|
InternetGatewayFor []Node `gorm:"foreignKey:InternetGatewayNodeID"`
|
|
// IsInternetGateway indicates if this node is an Internet
|
|
// Gateway node.
|
|
IsInternetGateway bool
|
|
|
|
Status string
|
|
DefaultACL string
|
|
Metadata string
|
|
Tags datatypes.JSONSlice[string]
|
|
PendingDelete bool
|
|
LastModified time.Time
|
|
LastCheckIn time.Time
|
|
LastPeerUpdate time.Time
|
|
ExpirationDateTime time.Time
|
|
}
|
|
|
|
type GatewayNodeConfig struct {
|
|
Range string
|
|
Range6 string
|
|
PersistentKeepalive int32
|
|
MTU int32
|
|
DNS string
|
|
}
|
|
|
|
type EgressGatewayNodeConfig struct {
|
|
NatEnabled bool
|
|
Ranges []RangeWithMetric
|
|
}
|
|
|
|
type RangeWithMetric struct {
|
|
Range string `json:"range"`
|
|
Metric uint32 `json:"metric"`
|
|
}
|
|
|
|
func (n *Node) TableName() string {
|
|
return "nodes_v1"
|
|
}
|
|
|
|
func (n *Node) Create(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(&Node{}).Create(n).Error
|
|
}
|
|
|
|
func (n *Node) Get(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(n).
|
|
Where("id = ?", n.ID).
|
|
First(n).
|
|
Error
|
|
}
|
|
|
|
func (n *Node) GetHost(ctx context.Context) error {
|
|
return db.FromContext(ctx).
|
|
Raw(`
|
|
SELECT hosts_v1.*
|
|
FROM hosts_v1
|
|
JOIN nodes_v1 ON hosts_v1.id = nodes_v1.host_id
|
|
WHERE nodes_v1.id = ?
|
|
`, n.ID).
|
|
Scan(&n.Host).
|
|
Error
|
|
}
|
|
|
|
func (n *Node) GetNetwork(ctx context.Context) error {
|
|
return db.FromContext(ctx).
|
|
Raw(`
|
|
SELECT networks_v1.*
|
|
FROM networks_v1
|
|
JOIN nodes_v1 ON networks_v1.id = nodes_v1.network_id
|
|
WHERE nodes_v1.id = ?
|
|
`, n.ID).
|
|
Scan(&n.Network).
|
|
Error
|
|
}
|
|
|
|
func (n *Node) GetByHostIDAndNetworkID(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(&Node{}).
|
|
Where("host_id = ? AND network_id = ?", n.HostID, n.NetworkID).
|
|
First(n).
|
|
Error
|
|
}
|
|
|
|
func (n *Node) ListAll(ctx context.Context) ([]Node, error) {
|
|
var nodes []Node
|
|
err := db.FromContext(ctx).Model(&Node{}).
|
|
Find(&nodes).
|
|
Error
|
|
return nodes, err
|
|
}
|
|
|
|
func (n *Node) ListZombies(ctx context.Context) ([]Node, error) {
|
|
var nodes []Node
|
|
err := db.FromContext(ctx).Model(&Node{}).
|
|
Where("id <> ? AND host_id = ? AND network_id = ?", n.ID, n.HostID, n.NetworkID).
|
|
Find(&nodes).
|
|
Error
|
|
return nodes, err
|
|
}
|
|
|
|
func (n *Node) ListExpired(ctx context.Context) ([]Node, error) {
|
|
var nodes []Node
|
|
err := db.FromContext(ctx).Model(&Node{}).
|
|
Where("expiration_date_time < ?", time.Now()).
|
|
First(&nodes).
|
|
Error
|
|
return nodes, err
|
|
}
|
|
|
|
func (n *Node) Exists(ctx context.Context) (bool, error) {
|
|
var exists bool
|
|
err := db.FromContext(ctx).Raw(
|
|
"SELECT EXISTS (SELECT 1 FROM nodes_v1 WHERE id = ?)",
|
|
n.ID,
|
|
).Scan(&exists).Error
|
|
return exists, err
|
|
}
|
|
|
|
func (n *Node) ExistsWithNetworkAndIPv4(ctx context.Context) (bool, error) {
|
|
var exists bool
|
|
err := db.FromContext(ctx).Raw(
|
|
"SELECT EXISTS (SELECT 1 FROM nodes_v1 WHERE network_id = ? AND address = ?)",
|
|
n.NetworkID,
|
|
n.Address,
|
|
).Scan(&exists).Error
|
|
return exists, err
|
|
}
|
|
|
|
func (n *Node) ExistsWithNetworkAndIPv6(ctx context.Context) (bool, error) {
|
|
var exists bool
|
|
err := db.FromContext(ctx).Raw(
|
|
"SELECT EXISTS (SELECT 1 FROM nodes_v1 WHERE network_id = ? AND address6 = ?)",
|
|
n.NetworkID,
|
|
n.Address6,
|
|
).Scan(&exists).Error
|
|
return exists, err
|
|
}
|
|
|
|
func (n *Node) Count(ctx context.Context) (int, error) {
|
|
var count int64
|
|
err := db.FromContext(ctx).Model(&Node{}).Count(&count).Error
|
|
return int(count), err
|
|
}
|
|
|
|
func (n *Node) CountByOS(ctx context.Context) (map[string]int, error) {
|
|
rows, err := db.FromContext(ctx).Raw(`
|
|
SELECT hosts_v1.os, COUNT(nodes_v1.id) as count
|
|
FROM hosts_v1 LEFT JOIN nodes_v1 ON hosts_v1.id = nodes_v1.host_id
|
|
GROUP BY hosts_v1.os
|
|
`).Rows()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(rows *sql.Rows) {
|
|
_ = rows.Close()
|
|
}(rows)
|
|
|
|
var os string
|
|
var count int
|
|
var countMap = make(map[string]int)
|
|
for rows.Next() {
|
|
err = rows.Scan(&os, &count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
countMap[os] = count
|
|
}
|
|
|
|
return countMap, nil
|
|
}
|
|
|
|
func (n *Node) Update(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(n).Updates(n).Error
|
|
}
|
|
|
|
func (n *Node) UpdateFailOverPeers(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(n).Update("fail_over_peers", n.FailOverPeers).Error
|
|
}
|
|
|
|
func (n *Node) UpdateLastCheckIn(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(n).Update("last_check_in", n.LastCheckIn).Error
|
|
}
|
|
|
|
func (n *Node) Upsert(ctx context.Context) error {
|
|
return db.FromContext(ctx).Save(n).Error
|
|
}
|
|
|
|
func (n *Node) Delete(ctx context.Context) error {
|
|
return db.FromContext(ctx).Model(n).Delete(n).Error
|
|
}
|
|
|
|
func (n *Node) IsEligibleToBeFailOver(ctx context.Context) (bool, error) {
|
|
var isEligible bool
|
|
err := db.FromContext(ctx).Raw(`
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM nodes_v1
|
|
JOIN networks_v1 ON nodes_v1.network_id = networks_v1.id
|
|
JOIN hosts_v1 ON nodes_v1.host_id = hosts_v1.id
|
|
WHERE nodes_v1.id = ? AND nodes_v1.gateway_node_id = ? AND networks_v1.fail_over_id = ? AND hosts_v1.os = ?
|
|
)`,
|
|
n.ID,
|
|
nil,
|
|
nil,
|
|
"linux",
|
|
).Scan(&isEligible).Error
|
|
return isEligible, err
|
|
}
|
|
|
|
func (n *Node) ResetAndRemoveFromFailOverPeers(ctx context.Context) error {
|
|
return db.FromContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
switch servercfg.GetDB() {
|
|
case "sqlite":
|
|
path := "$." + n.ID
|
|
err := tx.Model(&Node{}).
|
|
Where("network_id = ?", n.NetworkID).
|
|
Update("fail_over_peers", gorm.Expr("json_remove(fail_over_peers, ?)", path)).
|
|
Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "postgres":
|
|
err := tx.Model(&Node{}).
|
|
Where("network_id = ?", n.NetworkID).
|
|
Update("fail_over_peers", gorm.Expr("fail_over_peers - ?", n.ID)).
|
|
Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return tx.Model(n).
|
|
Update("fail_over_peers", datatypes.JSONMap{}).
|
|
Error
|
|
})
|
|
}
|