mirror of
https://github.com/gravitl/netmaker.git
synced 2026-10-12 02:15:06 +00:00
* wip(go): return auth token with user on create org owner; * wip(go): prevent use of default tenant slug ; * wip(go): upsert settings for tenant in ctx; * wip(go): use passed ctx when updating user; remove password validation from ValidateUser (already validated in UpdateUser); * wip(go): qualify all tenant id usages with table name; * wip(go): use passed ctx * wip(go): return old enrollment key type on create; * wip(go): prevent null networks return; * wip(go): wire ctx in dns/nameserver functions; wire ctx in host registration functions; * wip(go): wire ctx in node functions; * wip(go): wire ctx in node functions; * wip(go): wire ctx in extclient functions; * wip(go): remove not needed field; * wip(go): use passed ctx; * wip(go): use unique index instead of primary key; * wip(go): wire ctx in extclient apis; * wip(go): wire ctx in extclient apis; * wip(go): remove org id check from global scoped get tenant api; * wip(go): add scope slug to discover response; * wip(go): wire ctx in get users; * wip(go): set tenant id in posture check violations; * wip(go): upsert membership instead of user; * wip(go): pass correct len; * wip(go): optimize (single loop); * wip(go): unify todos; * wip(go): add todo; * wip(go): wire context for network get; * wip(go): wire context; * wip(go): wire context; * wip(go): wire ctx when getting tenant superadmin; * wip(go): add network creator to network admin group; * wip(go): check for user membership on sync; * wip(go): add methods with strict membership check; * wip(go): remove unwanted feature flags; * wip(go): report per-tenant usage during license validation; * wip(go): move email setting from tenant to org; * wip(go): pass org scope to email init; * wip(go): remove old tenant email settings usages; use org settings to get email settings * wip(go): use get email settings from org settings; * wip(go): remove email settings migration; * wip(go): migrate email settings from server to org settings; * wip(go): assign global network admin group membership only for super-admin and admins; * wip(go): assign global network admin group membership only for super-admin and admins; * wip(go): add membership alias to filter; * wip(go): skip clearing local license validation response cache; * wip(go): extend license validation response for multi-tenancy; * wip(go): add functions to get sole tenant and org; * wip(go): use tenant-specific feature flags; * wip(go): add tenant status type; * wip(go): remove old hook comment; * wip(go): expect ctx during license validation; * wip(go): always create default org and tenant for ce; * wip(go): add tenant-specific check for license validation error; * wip(go): cache license validation response in memory; * wip(go): add todo to handle multiple-tenant for integration pull; * wip(go): pass ctx during license validation; * wip(go): add todo for making changes later; run hook separately for each tenant; * wip(go): return specific errors when license validation response not in cache; * wip(go): use retryable.httpclient for calling license validation api; * wip(go): process license validation; * wip(go): license validation function; * wip(go): migration on license validation; * wip(go): org settings not expected; * wip(go): remove expiry from license validation response; * wip(go): add limits and limits enforcement to license validation response; * wip(go): remove expiry usages; * wip(go): add arg to clear cache; * wip(go): add arg to clear cache; * wip(go): skip license validation if cached resp; * wip(go): allow org_id and tenant_id query params; * wip(go): upsert tenant membership on sync; * wip(go): pass ctx to delete network roles; * wip(go): create tenant membership on org owner creation on ce; * wip(go): add tenant orchestrator; * wip(go): use tenant orchestrator; * wip(go): add default settings for tenant; * wip(go): make org owner superadmin on all tenants; * wip(go): simplify SyncOrgAndTenants; * wip(go): retry license validation only 3 times * wip(go): store invalid err; * wip(go): add function to check if limits are to be enforced on a tenant; * wip(go): pass tenant scope if possible when checking for license validation error; * wip(go): remove merge errors; * wip(go): add functions for limit checks; * wip(go): wire host limit check; * wip(go): wire ingress limit check; * wip(go): wire egress limit check; * wip(go): wire client limit check; * wip(go): wire network limit check; * wip(go): wire user limit check; * wip(go): use correct errors; * wip(go): skip telemetry for msps; * wip(go): disable telemetry on msp tenants; * wip(go): err check; * wip(go): uncomment log event; * wip(go): get sso state to get scope for non-headless oauth login; * wip(go): inline delete pending user; * wip(go): allow both org and tenant scope for pending users table; * wip(go): add migration for pending user table during set tenant id and rekey org/tenant id; * wip(go): update queries to use new scope fields in pending users table; * wip(go): allow both org and tenant scope for user invites table; * wip(go): update queries to use new scope fields; * wip(go): add migration for rekey-ing org/tenant id and setting tenant id; * wip(go): inline delete user invite; * wip(go): set scope when creating invite; * wip(go): get user count with membership is ctx is tenant scoped; * wip(go): add methods to upsert and delete org memberships; * wip(go): add global valid tenant and org roles map; * wip(go): add scope query in user invite get by email; * wip(go): add support for upsert and delete of org and tenant membership both; * wip(go): infer scope for user invite apis; * wip(go): wire ctx in get user invite by email; * wip(go): allow no scope headers to be passed in infer scope apis; * wip(go): add validation and checks for org-scoped invite api handlers; * wip(go): wire ctx to is pending user check; * wip(go): infer scope for pending user apis; * wip(go): set default role based on ctx scope; * wip(go): move mfa to tenant membership table; move account status to membership tables; * wip(go): migrate account status and mfa settings; * wip(go): update account status on sync; * wip(go): use update mfa to update mfa setting; * wip(go): set new membership settings on user creation; * wip(go): use membership where required; * wip(go): wire ctx on key unlink refs; * wip(go): use r.Context directly without reinitializing db; * wip(go): wire ctx in acls; * wip(go): add tenant scoped primary key constructor; * wip(go): use tenant scoped primary key for acls, dns, extclients and tags; * wip(go): wire ctx for get groups in network; * wip(go): wire ctx for get groups; * wip(go): scope non-uuid group ids by tenant id; * wip(go): migrate non-uuid group ids to scoped ids; * wip(go): don't expect scope id in invite validation and signup apis; * wip(go): wire ctx for get matching user; allow org scope when querying by external idp id; * wip(go): add mfa to org; * wip(go): scope preauth token; * wip(go): add fallback for older auth tokens; * wip(go): wire ctx for preauth token creation; * wip(go): migrate mfa settings; * wip(go): resolve mfa settings for inherited auth; * wip(go): update mfa for org; * wip(go): infer scope for totp apis; * wip(go): use ctx when passed; * wip(go): infer scope for list user apis; * wip(go): ensure set primary keys don't cause errors during fetch; * wip(go): make primary key for integrations uuid and store provider in separate key; * wip(go): migrate integration ids; * wip(go): use provider instead of id everywhere; * wip(go): remove gorm column; * wip(go): wire ctx for igw egress creation; * wip(go): wire ctx for igw egress update; * wip(go): wire ctx for host registration; * wip(go): wire ctx for host registration; * wip(go): merge errors; * wip(go): merge errors; * wip(go): wire ctx; remove default scope usages; * wip(go): use get with membership; * wip(go): org scope license validation; * wip(go): remove debug log; * wip(go): remove scoping on list roles; * wip(go): ignore invite creation if already exists; * wip(go): add is msp func to check if deployment is msp deployment; * wip(go): ensure username is email for msp users; * wip(go): add email validated check on user table; * wip(go): mark email validated when oauth user; * wip(go): handle gorm error; * wip(go): add tenant id to siem grpc schema; * wip(go): read tenant from scoped ctx; * wip(go): pass scoped ctx to siem grpc client; * wip(go): remove unused publisher; * wip(go): publish all tenant exporter updates on pull; * wip(go): fix wg usage; * wip(go): tenant scope the ctx when getting server info; * wip(go): add tenant id to enrollment key token; * wip(go): make host register tenant scope; * wip(go): add default settings; * wip(go): use correct checks for is msp tenant; * wip(go): add email validation extension; * wip(go): add pages for validation check; * wip(go): use extension for validating user; * wip(go): add invite type; * wip(go): create only one validation invite per user email; * wip(go): add validate email handler; * wip(go): add email builder; * wip(go): add user invite type migration logic; * wip(go): only log email send failures; * wip(go): add tenant id to flows table; * wip(go): add func to check if connected to clickhouse; * wip(go): migrate clickhouse; * wip(go): add tenant id to mdm and edr state; * wip(go): add delete methods for tables; * wip(go): add tenant teardown method; * wip(go): handle tenant deletion; * wip(go): clean up flow logs on tenant deletion; * wip(go): add update org membership role id method; * wip(go): add transfer org ownership api; * wip(go): move audit logs retention period to org settings; * wip(go):get retention period from org settings; * wip(go): remove old audit logs usage; * wip(go): migrate audit logs retention period; * wip(go): unmarshal decodes nums as float64; * wip(go): run flow logs cleanup for all tenants in one go; * wip(go): add audit logs retention period and smtp settings to tenant settings table for non-msp tenants; * wip(go): resolve retention period and smtp settings correctly; * wip(go): handle retention period and smtp settings updates and fetches correctly; * wip(go): overlay retention period and smtp settings on server settings;; * wip(go): upsert overlayed settings; * wip(go): add azure tenant id to org settings; * wip(go): tenant scope list roles; * wip(go): add noop creds; * wip(go): use first instead of find; * wip(go): use correct roles when transfering ownership; * wip(go): delete access tokens only if tenant scoped; * wip(go): make delete user work for both org and tenant scope; * wip(go): delete extclients only if tenant scoped; * wip(go): check if caller is allowed to delete user; * wip(go): infer scope for update user; * wip(go): prevent org-owner elevation; * wip(go): permissions check for update; * wip(go): delete user access tokens only if tenant scope; * wip(go): delete extclients only if tenant scope; * wip(go): move upsert membership in upsert user; * wip(go): set api host when constructing server config; * wip(go): fix static errors; * wip(go): fix test cases; * wip(go): use correct get ns order; * wip(go): use correct get pending host order; * wip(go): use correct get access token order; * wip(go): use correct get event order; * wip(go): return error response on emqx user creation error; * wip(go): store correct value in cache; * wip(go): return better error response; * wip(go): return better error response; * wip(go): use cache when listing egress acls; * wip(go): wire ctx in UserHasNetworkGroupAccess; * wip(go): remove double scope wrapping; * wip(go): add security check for list and get org; * wip(go): ratelimit discover api; * wip(go): expect sole org id in get and create org owner; * wip(go): wrap create org owner in tx;
553 lines
17 KiB
Go
553 lines
17 KiB
Go
package mq
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gravitl/netmaker/db"
|
|
"github.com/gravitl/netmaker/logger"
|
|
"github.com/gravitl/netmaker/logic"
|
|
"github.com/gravitl/netmaker/models"
|
|
"github.com/gravitl/netmaker/schema"
|
|
"github.com/gravitl/netmaker/scope"
|
|
"github.com/gravitl/netmaker/servercfg"
|
|
"golang.org/x/exp/slog"
|
|
)
|
|
|
|
var (
|
|
peerUpdateSignals sync.Map
|
|
peerUpdateReplaces sync.Map
|
|
)
|
|
|
|
const (
|
|
peerUpdateDebounce = 500 * time.Millisecond
|
|
peerUpdateMaxWait = 3 * time.Second
|
|
maxConcurrentPublishes = 5
|
|
)
|
|
|
|
var metricsHTTPClient = &http.Client{Timeout: 30 * time.Second}
|
|
|
|
func peerUpdateSignalFor(tenantID string) chan struct{} {
|
|
ch, _ := peerUpdateSignals.LoadOrStore(tenantID, make(chan struct{}, 1))
|
|
return ch.(chan struct{})
|
|
}
|
|
|
|
func peerUpdateReplaceFor(tenantID string) *atomic.Bool {
|
|
b, _ := peerUpdateReplaces.LoadOrStore(tenantID, new(atomic.Bool))
|
|
return b.(*atomic.Bool)
|
|
}
|
|
|
|
// PublishPeerUpdate --- queues a peer update for a tenant that will be coalesced with other
|
|
// rapid-fire updates via a debounce window (500ms) capped by a max-wait (3s).
|
|
func PublishPeerUpdate(ctx context.Context, replacePeers bool) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
tenantID := scope.ID(ctx)
|
|
if replacePeers {
|
|
peerUpdateReplaceFor(tenantID).Store(true)
|
|
}
|
|
select {
|
|
case peerUpdateSignalFor(tenantID) <- struct{}{}:
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartPeerUpdateWorker --- runs a background goroutine for a given tenant that coalesces peer
|
|
// update signals using a resettable debounce timer capped by an absolute
|
|
// max-wait deadline. This ensures rapid-fire PublishPeerUpdate calls result
|
|
// in a single broadcast, while guaranteeing peers never wait longer than
|
|
// peerUpdateMaxWait from the first signal.
|
|
func StartPeerUpdateWorker(ctx context.Context) {
|
|
tenantID := scope.ID(ctx)
|
|
signal := peerUpdateSignalFor(tenantID)
|
|
replace := peerUpdateReplaceFor(tenantID)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-signal:
|
|
maxWait := time.After(peerUpdateMaxWait)
|
|
debounce := time.After(peerUpdateDebounce)
|
|
wait:
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-maxWait:
|
|
break wait
|
|
case <-debounce:
|
|
break wait
|
|
case <-signal:
|
|
debounce = time.After(peerUpdateDebounce)
|
|
}
|
|
}
|
|
replacePeers := replace.Swap(false)
|
|
drain:
|
|
for {
|
|
select {
|
|
case <-signal:
|
|
default:
|
|
break drain
|
|
}
|
|
}
|
|
logic.RefreshHostPeerInfoCache(ctx)
|
|
if err := publishPeerUpdateImmediate(ctx, replacePeers); err != nil {
|
|
slog.Error("error publishing peer update", "tenant", tenantID, "error", err)
|
|
} else {
|
|
publishServerSync(ctx, logic.SyncTypePeerUpdate)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// warmPeerCaches pre-computes HostPeerInfo and HostPeerUpdate caches for a tenant so that
|
|
// pull requests arriving before the first debounced broadcast are served
|
|
// instantly from cache instead of triggering expensive on-demand computation.
|
|
func warmPeerCaches(ctx context.Context) {
|
|
hosts, allNodes := logic.RefreshHostPeerInfoCache(ctx)
|
|
if hosts == nil || allNodes == nil {
|
|
return
|
|
}
|
|
for i := range hosts {
|
|
peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", &hosts[i], allNodes, nil, nil, nil)
|
|
if err != nil {
|
|
slog.Error("warmPeerCaches: failed to compute peer update", "host", hosts[i].ID, "error", err)
|
|
continue
|
|
}
|
|
logic.StoreHostPeerUpdate(ctx, hosts[i].ID.String(), peerUpdate)
|
|
}
|
|
slog.Info("peer update caches warmed", "tenant", scope.ID(ctx), "hosts", len(hosts))
|
|
}
|
|
|
|
// publishPeerUpdateImmediate --- determines and publishes a peer update to all the hosts in a given tenant.
|
|
func publishPeerUpdateImmediate(ctx context.Context, replacePeers bool) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
|
|
if logic.GetManageDNS(ctx) {
|
|
sendDNSSync(ctx)
|
|
}
|
|
|
|
hosts, err := (&schema.Host{}).ListAll(ctx)
|
|
if err != nil {
|
|
logger.Log(1, "err getting all hosts", err.Error())
|
|
return err
|
|
}
|
|
allNodes, err := logic.GetAllNodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sem := make(chan struct{}, maxConcurrentPublishes)
|
|
var wg sync.WaitGroup
|
|
for _, host := range hosts {
|
|
host := host
|
|
sem <- struct{}{}
|
|
wg.Add(1)
|
|
go func(host schema.Host) {
|
|
defer func() { <-sem; wg.Done() }()
|
|
if err := PublishSingleHostPeerUpdate(ctx, &host, allNodes, nil, nil, nil, replacePeers, nil); err != nil {
|
|
id := host.Name
|
|
if host.ID != uuid.Nil {
|
|
id = host.ID.String()
|
|
}
|
|
slog.Error("failed to publish peer update to host", id, ": ", err)
|
|
}
|
|
}(host)
|
|
}
|
|
wg.Wait()
|
|
|
|
return nil
|
|
}
|
|
|
|
// PublishDeletedNodePeerUpdate --- determines and publishes a peer update
|
|
// to all the hosts with a deleted node to account for
|
|
func PublishDeletedNodePeerUpdate(ctx context.Context, delHost *schema.Host, delNode *models.Node) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
|
|
hosts, err := (&schema.Host{}).ListAll(ctx)
|
|
if err != nil {
|
|
logger.Log(1, "err getting all hosts", err.Error())
|
|
return err
|
|
}
|
|
allNodes, err := logic.GetAllNodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, host := range hosts {
|
|
host := host
|
|
if err = PublishSingleHostPeerUpdate(ctx, &host, allNodes, delHost, delNode, nil, false, nil); err != nil {
|
|
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// PublishDeletedClientPeerUpdate --- determines and publishes a peer update
|
|
// to all the hosts with a deleted ext client to account for
|
|
func PublishDeletedClientPeerUpdate(ctx context.Context, delClient *models.ExtClient) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
|
|
hosts, err := (&schema.Host{}).ListAll(ctx)
|
|
if err != nil {
|
|
logger.Log(1, "err getting all hosts", err.Error())
|
|
return err
|
|
}
|
|
nodes, err := logic.GetAllNodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, host := range hosts {
|
|
host := host
|
|
if host.OS != models.OS_Types.IoT {
|
|
if err = PublishSingleHostPeerUpdate(ctx, &host, nodes, nil, nil, []models.ExtClient{*delClient}, false, nil); err != nil {
|
|
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
|
|
func PublishSingleHostPeerUpdate(ctx context.Context, host *schema.Host, allNodes []models.Node, deletedHost *schema.Host, deletedNode *models.Node, deletedClients []models.ExtClient, replacePeers bool, wg *sync.WaitGroup) error {
|
|
if wg != nil {
|
|
defer wg.Done()
|
|
}
|
|
peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, allNodes, deletedHost, deletedNode, deletedClients)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, nodeID := range host.Nodes {
|
|
|
|
node, err := logic.GetNodeByID(nodeID)
|
|
if err == nil && node.Connected && logic.InternetExitRoutingNodeID(&node) != "" {
|
|
replacePeers = false
|
|
}
|
|
}
|
|
peerUpdate.OldPeerUpdateFields = models.OldPeerUpdateFields{
|
|
NodePeers: peerUpdate.NodePeers,
|
|
OldPeers: peerUpdate.Peers,
|
|
EndpointDetection: peerUpdate.ServerConfig.EndpointDetection,
|
|
}
|
|
peerUpdate.ReplacePeers = replacePeers
|
|
if deletedNode == nil && len(deletedClients) == 0 {
|
|
logic.StoreHostPeerUpdate(ctx, host.ID.String(), peerUpdate)
|
|
}
|
|
data, err := json.Marshal(&peerUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
slog.Debug("publishing peer update to host", "name", host.Name, "id", host.ID.String(), "owner", host.OwnerUsername, "peers", len(peerUpdate.Peers))
|
|
return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
|
|
}
|
|
|
|
// PublishPeerUpdatesToExitClientHosts pushes a peer update to each unique host among
|
|
// the given exit-node clients (no global mesh update). Used to fail-open full-tunnel
|
|
// routes before a routing node is removed.
|
|
func PublishPeerUpdatesToExitClientHosts(ctx context.Context, clientNodes []models.Node) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
allNodes, err := logic.GetAllNodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
seenHosts := map[string]struct{}{}
|
|
for i := range clientNodes {
|
|
hostID := clientNodes[i].HostID.String()
|
|
if hostID == "" || hostID == uuid.Nil.String() {
|
|
continue
|
|
}
|
|
if _, ok := seenHosts[hostID]; ok {
|
|
continue
|
|
}
|
|
seenHosts[hostID] = struct{}{}
|
|
host := &schema.Host{ID: clientNodes[i].HostID}
|
|
if err := host.Get(ctx); err != nil {
|
|
slog.Error("exit-client peer update: failed to get host", "host", hostID, "error", err)
|
|
continue
|
|
}
|
|
if err := PublishSingleHostPeerUpdate(ctx, host, allNodes, nil, nil, nil, false, nil); err != nil {
|
|
slog.Error("exit-client peer update: publish failed", "host", host.Name, "error", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PublishPeerUpdatesForExitClientsFirst pushes a peer update to each unique host among
|
|
// exit-node clients synchronously, then queues a global peer update. Call this when an
|
|
// exit becomes unavailable (routing node disconnect or internet egress disabled) so
|
|
// clients drop full-tunnel routes before the rest of the mesh updates.
|
|
func PublishPeerUpdatesForExitClientsFirst(ctx context.Context, clientNodes []models.Node) error {
|
|
if err := PublishPeerUpdatesToExitClientHosts(ctx, clientNodes); err != nil {
|
|
_ = PublishPeerUpdate(ctx, false)
|
|
return err
|
|
}
|
|
return PublishPeerUpdate(ctx, false)
|
|
}
|
|
|
|
// NodeUpdate -- publishes a node update
|
|
func NodeUpdate(node *models.Node) error {
|
|
host := &schema.Host{ID: node.HostID}
|
|
if err := host.Get(db.WithContext(context.TODO())); err != nil {
|
|
return nil
|
|
}
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
logger.Log(3, "publishing node update to "+node.ID.String())
|
|
|
|
//if len(node.NetworkSettings.AccessKeys) > 0 {
|
|
//node.NetworkSettings.AccessKeys = []models.AccessKey{} // not to be sent (don't need to spread access keys around the network; we need to know how to reach other nodes, not become them)
|
|
//}
|
|
|
|
data, err := json.Marshal(node)
|
|
if err != nil {
|
|
logger.Log(2, "error marshalling node update ", err.Error())
|
|
return err
|
|
}
|
|
if err = publish(host, fmt.Sprintf("node/update/%s/%s", node.Network, node.ID), data); err != nil {
|
|
logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HostUpdate -- publishes a host update to clients
|
|
func HostUpdate(hostUpdate *models.HostUpdate) error {
|
|
if !servercfg.IsMessageQueueBackend() {
|
|
return nil
|
|
}
|
|
logger.Log(3, "publishing host update to "+hostUpdate.Host.ID.String())
|
|
|
|
data, err := json.Marshal(hostUpdate)
|
|
if err != nil {
|
|
logger.Log(2, "error marshalling node update ", err.Error())
|
|
return err
|
|
}
|
|
if err = publish(&hostUpdate.Host, fmt.Sprintf("host/update/%s/%s", hostUpdate.Host.ID.String(), servercfg.GetServer()), data); err != nil {
|
|
logger.Log(2, "error publishing host update to", hostUpdate.Host.ID.String(), err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ServerStartNotify - notifies all non server nodes to pull changes after a restart
|
|
func ServerStartNotify() error {
|
|
nodes, err := logic.GetAllNodes(db.WithContext(context.Background()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := range nodes {
|
|
nodes[i].Action = schema.NODE_FORCE_UPDATE
|
|
if err = NodeUpdate(&nodes[i]); err != nil {
|
|
logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PublishMqUpdatesForDeletedNode - published all the required updates for deleted host and node
|
|
func PublishMqUpdatesForDeletedNode(ctx context.Context, delHost *schema.Host, node models.Node, sendNodeUpdate bool) {
|
|
// notify of peer change
|
|
node.PendingDelete = true
|
|
node.Action = schema.NODE_DELETE
|
|
if sendNodeUpdate {
|
|
if err := NodeUpdate(&node); err != nil {
|
|
slog.Error("error publishing node update to node", "node", node.ID, "error", err)
|
|
}
|
|
}
|
|
if err := PublishDeletedNodePeerUpdate(ctx, delHost, &node); err != nil {
|
|
logger.Log(1, "error publishing peer update ", err.Error())
|
|
}
|
|
}
|
|
|
|
// PushAllMetricsToExporter fetches all node metrics from the database
|
|
// and POSTs them as a batch to the exporter's HTTP API.
|
|
// Called periodically by a ticker instead of on every individual metrics MQTT message.
|
|
func PushAllMetricsToExporter(ctx context.Context) {
|
|
if !servercfg.IsMetricsExporter() {
|
|
return
|
|
}
|
|
baseURL := servercfg.GetMetricsExporterURL()
|
|
healthResp, err := metricsHTTPClient.Get(baseURL + "/api/health")
|
|
if err != nil {
|
|
slog.Warn("metrics export: exporter not reachable, skipping", "error", err)
|
|
return
|
|
}
|
|
healthResp.Body.Close()
|
|
if healthResp.StatusCode != http.StatusOK {
|
|
slog.Warn("metrics export: exporter unhealthy, skipping", "status", healthResp.StatusCode)
|
|
return
|
|
}
|
|
metricRecords, err := (&schema.MetricsRecord{}).List(ctx)
|
|
if err != nil {
|
|
slog.Error("metrics export: failed to fetch records", "error", err)
|
|
return
|
|
}
|
|
batch := make([]models.Metrics, 0, len(metricRecords))
|
|
for _, r := range metricRecords {
|
|
batch = append(batch, r.Value.Data())
|
|
}
|
|
if len(batch) == 0 {
|
|
return
|
|
}
|
|
body, err := json.Marshal(batch)
|
|
if err != nil {
|
|
slog.Error("metrics export: failed to marshal batch", "error", err)
|
|
return
|
|
}
|
|
url := baseURL + "/api/v1/metrics"
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
|
if err != nil {
|
|
slog.Error("metrics export: failed to create request", "error", err)
|
|
return
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
metricsUser := os.Getenv("METRICS_USERNAME")
|
|
if metricsUser == "" {
|
|
metricsUser = "netmaker"
|
|
}
|
|
req.SetBasicAuth(metricsUser, os.Getenv("METRICS_SECRET"))
|
|
resp, err := metricsHTTPClient.Do(req)
|
|
if err != nil {
|
|
slog.Error("metrics export: POST failed", "url", url, "error", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
slog.Error("metrics export: unexpected status", "url", url, "status", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
// sendPeers - retrieve networks, send peer ports to all peers
|
|
func sendPeers() {
|
|
peer_force_send++
|
|
if peer_force_send == 5 {
|
|
servercfg.SetHost()
|
|
peer_force_send = 0
|
|
// Only run timer checkpoint on master pod in HA setup
|
|
if servercfg.IsMasterPod() {
|
|
err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
|
|
if err != nil {
|
|
logger.Log(3, "error occurred on timer,", err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func SendDNSSyncByNetwork(ctx context.Context, network string) error {
|
|
|
|
k, err := logic.GetDNS(ctx, network)
|
|
k = append(k, logic.EgressDNs(ctx, network)...)
|
|
if err == nil && len(k) > 0 {
|
|
err = PushSyncDNS(k)
|
|
if err != nil {
|
|
slog.Warn("error publishing dns entry data for network ", network, err.Error())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func sendDNSSync(ctx context.Context) error {
|
|
networks, err := (&schema.Network{}).ListAll(db.WithContext(ctx))
|
|
if err == nil && len(networks) > 0 {
|
|
for _, v := range networks {
|
|
k, err := logic.GetDNS(ctx, v.Name)
|
|
k = append(k, logic.EgressDNs(ctx, v.Name)...)
|
|
if err == nil && len(k) > 0 {
|
|
err = PushSyncDNS(k)
|
|
if err != nil {
|
|
slog.Warn("error publishing dns entry data for network ", v.Name, err.Error())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func PushSyncDNS(dnsEntries []models.DNSEntry) error {
|
|
logger.Log(2, "----> Pushing Sync DNS")
|
|
data, err := json.Marshal(dnsEntries)
|
|
if err != nil {
|
|
return errors.New("failed to marshal DNS entries: " + err.Error())
|
|
}
|
|
if mqclient == nil || !mqclient.IsConnectionOpen() {
|
|
return errors.New("cannot publish ... mqclient not connected")
|
|
}
|
|
if token := mqclient.Publish(fmt.Sprintf("host/dns/sync/%s/%s", dnsEntries[0].Network, servercfg.GetServer()), 0, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
|
|
var err error
|
|
if token.Error() == nil {
|
|
err = errors.New("connection timeout")
|
|
} else {
|
|
err = token.Error()
|
|
}
|
|
return err
|
|
}
|
|
// todo(nm-341): is this good for multi-tenancy?
|
|
if !servercfg.DeployedByOperator() {
|
|
if token := mqclient.Publish(fmt.Sprintf("host/dns/sync/%s", dnsEntries[0].Network), 0, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
|
|
var err error
|
|
if token.Error() == nil {
|
|
err = errors.New("connection timeout")
|
|
} else {
|
|
err = token.Error()
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func PublishIntegrationUpsert(id string) error {
|
|
if token := mqclient.Publish(fmt.Sprintf("integration/%s/%s/upsert", servercfg.GetServer(), id), 0, true, []byte{}); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
|
|
var err error
|
|
if token.Error() == nil {
|
|
err = errors.New("connection timeout")
|
|
} else {
|
|
err = token.Error()
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func PublishIntegrationDelete(id string) error {
|
|
if token := mqclient.Publish(fmt.Sprintf("integration/%s/%s/delete", servercfg.GetServer(), id), 0, true, []byte{}); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
|
|
var err error
|
|
if token.Error() == nil {
|
|
err = errors.New("connection timeout")
|
|
} else {
|
|
err = token.Error()
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|