Added functionality to track the last refreshed timestamp for CDN sources. Updated the database schema and relevant methods to include the last refreshed timestamp during creation and updates. Implemented logic to skip fetching CDN sources based on their refresh interval, improving efficiency in the module prefix collection process. Enhanced the data retrieval methods to support the new timestamp field, ensuring accurate state management for CDN sources.
833 lines
19 KiB
Go
833 lines
19 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var _ Backend = (*Memory)(nil)
|
|
|
|
func (m *Memory) CreateModule(tenantID string, in *Module) (*Module, error) {
|
|
if in == nil || strings.TrimSpace(in.Type) == "" || strings.TrimSpace(in.Name) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
mod := &Module{
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
Type: in.Type,
|
|
Name: strings.TrimSpace(in.Name),
|
|
Enabled: in.Enabled,
|
|
Priority: in.Priority,
|
|
RefreshIntervalSec: in.RefreshIntervalSec,
|
|
CronExpr: in.CronExpr,
|
|
DefaultCommunityID: in.DefaultCommunityID,
|
|
DohProfileID: in.DohProfileID,
|
|
}
|
|
m.modules[id] = mod
|
|
return mod, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateModule(tenantID, moduleID string, patch *ModulePatch) (*Module, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, ok := m.modules[moduleID]
|
|
if !ok || mod.DeletedAt != nil || mod.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Name != nil {
|
|
mod.Name = strings.TrimSpace(*patch.Name)
|
|
}
|
|
if patch.Enabled != nil {
|
|
mod.Enabled = *patch.Enabled
|
|
}
|
|
if patch.Priority != nil {
|
|
mod.Priority = *patch.Priority
|
|
}
|
|
if patch.RefreshIntervalSec != nil {
|
|
mod.RefreshIntervalSec = *patch.RefreshIntervalSec
|
|
}
|
|
if patch.CronExpr != nil {
|
|
mod.CronExpr = *patch.CronExpr
|
|
}
|
|
if patch.DefaultCommunityID != nil {
|
|
v := strings.TrimSpace(*patch.DefaultCommunityID)
|
|
if v == "" {
|
|
mod.DefaultCommunityID = nil
|
|
} else {
|
|
mod.DefaultCommunityID = &v
|
|
}
|
|
}
|
|
if patch.DohProfileID != nil {
|
|
v := strings.TrimSpace(*patch.DohProfileID)
|
|
if v == "" {
|
|
mod.DohProfileID = nil
|
|
} else {
|
|
mod.DohProfileID = &v
|
|
}
|
|
}
|
|
return mod, nil
|
|
}
|
|
|
|
func (m *Memory) SoftDeleteModule(tenantID, moduleID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, ok := m.modules[moduleID]
|
|
if !ok || mod.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
now := time.Now().UTC()
|
|
mod.DeletedAt = &now
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) moduleWriteOK(tenantID, moduleID string) (*Module, error) {
|
|
mod, ok := m.modules[moduleID]
|
|
if !ok || mod.DeletedAt != nil || mod.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return mod, nil
|
|
}
|
|
|
|
func (m *Memory) ListCDNSources(tenantID, moduleID string) ([]*CDNSource, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "CDN_CIDRS" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
var out []*CDNSource
|
|
for _, s := range m.cdnSources {
|
|
if s.ModuleID == moduleID {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) CreateCDNSource(tenantID, moduleID string, in *CDNSource) (*CDNSource, error) {
|
|
if in == nil || strings.TrimSpace(in.URL) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "CDN_CIDRS" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
id := uuid.NewString()
|
|
s := &CDNSource{
|
|
ID: id,
|
|
ModuleID: moduleID,
|
|
SourceKind: in.SourceKind,
|
|
URL: strings.TrimSpace(in.URL),
|
|
PrefixPath: strings.TrimSpace(in.PrefixPath),
|
|
Etag: in.Etag,
|
|
RefreshIntervalSec: in.RefreshIntervalSec,
|
|
CommunityID: in.CommunityID,
|
|
LastRefreshedAt: in.LastRefreshedAt,
|
|
}
|
|
m.cdnSources[id] = s
|
|
return s, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateCDNSource(tenantID, moduleID, sourceID string, patch *CDNSourcePatch) (*CDNSource, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return nil, err
|
|
}
|
|
s, ok := m.cdnSources[sourceID]
|
|
if !ok || s.ModuleID != moduleID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.SourceKind != nil {
|
|
s.SourceKind = *patch.SourceKind
|
|
}
|
|
if patch.URL != nil {
|
|
s.URL = strings.TrimSpace(*patch.URL)
|
|
}
|
|
if patch.PrefixPath != nil {
|
|
s.PrefixPath = strings.TrimSpace(*patch.PrefixPath)
|
|
}
|
|
if patch.Etag != nil {
|
|
s.Etag = *patch.Etag
|
|
}
|
|
if patch.RefreshIntervalSec != nil {
|
|
s.RefreshIntervalSec = patch.RefreshIntervalSec
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
s.CommunityID = nil
|
|
} else {
|
|
s.CommunityID = &v
|
|
}
|
|
}
|
|
if patch.LastRefreshedAt != nil {
|
|
t := patch.LastRefreshedAt.UTC()
|
|
s.LastRefreshedAt = &t
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (m *Memory) DeleteCDNSource(tenantID, moduleID, sourceID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
s, ok := m.cdnSources[sourceID]
|
|
if !ok || s.ModuleID != moduleID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.cdnSources, sourceID)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListASEntries(tenantID, moduleID string) ([]*ASEntry, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "AS_PREFIXES" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
var out []*ASEntry
|
|
for _, e := range m.asEntries {
|
|
if e.ModuleID == moduleID {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) CreateASEntry(tenantID, moduleID string, in *ASEntry) (*ASEntry, error) {
|
|
if in == nil || !ValidASN(in.ASN) {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "AS_PREFIXES" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
id := uuid.NewString()
|
|
e := &ASEntry{ID: id, ModuleID: moduleID, ASN: in.ASN, CommunityID: in.CommunityID}
|
|
m.asEntries[id] = e
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateASEntry(tenantID, moduleID, entryID string, patch *ASEntryPatch) (*ASEntry, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return nil, err
|
|
}
|
|
e, ok := m.asEntries[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return nil, ErrNotFound
|
|
}
|
|
prevASN := e.ASN
|
|
if patch.ASN != nil {
|
|
e.ASN = *patch.ASN
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
e.CommunityID = nil
|
|
} else {
|
|
e.CommunityID = &v
|
|
}
|
|
}
|
|
if !ValidASN(e.ASN) {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
if patch.ASN != nil && e.ASN != prevASN {
|
|
e.ASNName = ""
|
|
e.PrefixCount = nil
|
|
e.ASNResolvedAt = nil
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateASEntryResolveMeta(tenantID, moduleID, entryID string, asnName string, prefixCount int64, resolvedAt time.Time) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
e, ok := m.asEntries[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return ErrNotFound
|
|
}
|
|
e.ASNName = strings.TrimSpace(asnName)
|
|
pc := prefixCount
|
|
e.PrefixCount = &pc
|
|
t := resolvedAt.UTC()
|
|
e.ASNResolvedAt = &t
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) DeleteASEntry(tenantID, moduleID, entryID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
e, ok := m.asEntries[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.asEntries, entryID)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListDomainEntries(tenantID, moduleID string) ([]*DomainEntry, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "DOMAINS" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
var out []*DomainEntry
|
|
for _, e := range m.domainEnt {
|
|
if e.ModuleID == moduleID {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) CreateDomainEntry(tenantID, moduleID string, in *DomainEntry) (*DomainEntry, error) {
|
|
if in == nil || strings.TrimSpace(in.FQDN) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "DOMAINS" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
id := uuid.NewString()
|
|
e := &DomainEntry{ID: id, ModuleID: moduleID, FQDN: strings.TrimSpace(in.FQDN), CommunityID: in.CommunityID}
|
|
m.domainEnt[id] = e
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateDomainEntry(tenantID, moduleID, entryID string, patch *DomainEntryPatch) (*DomainEntry, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return nil, err
|
|
}
|
|
e, ok := m.domainEnt[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.FQDN != nil {
|
|
e.FQDN = strings.TrimSpace(*patch.FQDN)
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
e.CommunityID = nil
|
|
} else {
|
|
e.CommunityID = &v
|
|
}
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) DeleteDomainEntry(tenantID, moduleID, entryID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
e, ok := m.domainEnt[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.domainEnt, entryID)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListIPRangeEntries(tenantID, moduleID string) ([]*IPRangeEntry, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "IP_RANGES" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
var out []*IPRangeEntry
|
|
for _, e := range m.ipRanges {
|
|
if e.ModuleID == moduleID {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) CreateIPRangeEntry(tenantID, moduleID string, in *IPRangeEntry) (*IPRangeEntry, error) {
|
|
if in == nil || strings.TrimSpace(in.Prefix) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mod.Type != "IP_RANGES" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
id := uuid.NewString()
|
|
e := &IPRangeEntry{ID: id, ModuleID: moduleID, Prefix: strings.TrimSpace(in.Prefix), CommunityID: in.CommunityID}
|
|
m.ipRanges[id] = e
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateIPRangeEntry(tenantID, moduleID, entryID string, patch *IPRangePatch) (*IPRangeEntry, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return nil, err
|
|
}
|
|
e, ok := m.ipRanges[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Prefix != nil {
|
|
e.Prefix = strings.TrimSpace(*patch.Prefix)
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
e.CommunityID = nil
|
|
} else {
|
|
e.CommunityID = &v
|
|
}
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
func (m *Memory) DeleteIPRangeEntry(tenantID, moduleID, entryID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
e, ok := m.ipRanges[entryID]
|
|
if !ok || e.ModuleID != moduleID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.ipRanges, entryID)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListDohProfiles(tenantID string) ([]*DohProfile, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
var out []*DohProfile
|
|
for _, p := range m.dohProfiles {
|
|
if p.TenantID == tenantID {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) GetDohProfile(tenantID, id string) (*DohProfile, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
p, ok := m.dohProfiles[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) CreateDohProfile(tenantID string, in *DohProfile) (*DohProfile, error) {
|
|
if in == nil || strings.TrimSpace(in.URL) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
p := &DohProfile{
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
Name: in.Name,
|
|
URL: strings.TrimSpace(in.URL),
|
|
TimeoutMs: in.TimeoutMs,
|
|
SecretRef: in.SecretRef,
|
|
}
|
|
m.dohProfiles[id] = p
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateDohProfile(tenantID, id string, patch *DohProfilePatch) (*DohProfile, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.dohProfiles[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Name != nil {
|
|
p.Name = *patch.Name
|
|
}
|
|
if patch.URL != nil {
|
|
p.URL = strings.TrimSpace(*patch.URL)
|
|
}
|
|
if patch.TimeoutMs != nil {
|
|
p.TimeoutMs = patch.TimeoutMs
|
|
}
|
|
if patch.SecretRef != nil {
|
|
p.SecretRef = patch.SecretRef
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) DeleteDohProfile(tenantID, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.dohProfiles[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
for _, mod := range m.modules {
|
|
if mod.DohProfileID != nil && *mod.DohProfileID == id {
|
|
return ErrInvalidInput
|
|
}
|
|
}
|
|
delete(m.dohProfiles, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListCommunities(tenantID string) ([]*Community, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
var out []*Community
|
|
for _, c := range m.communities {
|
|
if c.TenantID == tenantID {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) GetCommunity(tenantID, id string) (*Community, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
c, ok := m.communities[id]
|
|
if !ok || c.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (m *Memory) CreateCommunity(tenantID string, in *Community) (*Community, error) {
|
|
if in == nil || strings.TrimSpace(in.Community) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
vj := in.ValueJSON
|
|
if strings.TrimSpace(vj) == "" {
|
|
vj = "{}"
|
|
}
|
|
c := &Community{ID: id, TenantID: tenantID, Community: strings.TrimSpace(in.Community), Title: strings.TrimSpace(in.Title), ValueJSON: vj}
|
|
m.communities[id] = c
|
|
return c, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateCommunity(tenantID, id string, patch *CommunityPatch) (*Community, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
c, ok := m.communities[id]
|
|
if !ok || c.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Community != nil {
|
|
c.Community = strings.TrimSpace(*patch.Community)
|
|
}
|
|
if patch.Title != nil {
|
|
c.Title = strings.TrimSpace(*patch.Title)
|
|
}
|
|
if patch.ValueJSON != nil {
|
|
c.ValueJSON = *patch.ValueJSON
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (m *Memory) DeleteCommunity(tenantID, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
c, ok := m.communities[id]
|
|
if !ok || c.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
_ = c
|
|
// weak check: modules default_community
|
|
for _, mod := range m.modules {
|
|
if mod.DefaultCommunityID != nil && *mod.DefaultCommunityID == id {
|
|
return ErrInvalidInput
|
|
}
|
|
}
|
|
delete(m.communities, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetPeer(tenantID, id string) (*BGPPeer, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
p, ok := m.peers[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) CreatePeer(tenantID string, in *BGPPeer) (*BGPPeer, error) {
|
|
if in == nil || in.RemoteASN == 0 {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
neighbor, ok := NormalizePeerNeighborString(in.Neighbor)
|
|
if !ok {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
p := &BGPPeer{
|
|
ID: id, TenantID: tenantID, SpeakerID: in.SpeakerID, Name: in.Name,
|
|
Neighbor: neighbor, RemoteASN: in.RemoteASN,
|
|
Enabled: EffectivePeerEnabledOnCreate(in.Enabled, in.SessionState),
|
|
SessionState: in.SessionState, PoliciesJSON: in.PoliciesJSON,
|
|
}
|
|
m.peers[id] = p
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) UpdatePeer(tenantID, id string, patch *PeerPatch) (*BGPPeer, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.peers[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Neighbor != nil {
|
|
n, ok := NormalizePeerNeighborString(*patch.Neighbor)
|
|
if !ok {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
p.Neighbor = n
|
|
}
|
|
if patch.RemoteASN != nil {
|
|
p.RemoteASN = *patch.RemoteASN
|
|
}
|
|
if patch.SpeakerID != nil {
|
|
v := strings.TrimSpace(*patch.SpeakerID)
|
|
if v == "" {
|
|
p.SpeakerID = nil
|
|
} else {
|
|
p.SpeakerID = &v
|
|
}
|
|
}
|
|
if patch.Enabled != nil {
|
|
p.Enabled = *patch.Enabled
|
|
}
|
|
if patch.Name != nil {
|
|
p.Name = *patch.Name
|
|
}
|
|
if patch.SessionState != nil {
|
|
p.SessionState = *patch.SessionState
|
|
}
|
|
if patch.PoliciesJSON != nil {
|
|
p.PoliciesJSON = *patch.PoliciesJSON
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (m *Memory) DeletePeer(tenantID, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.peers[id]
|
|
if !ok || p.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.peers, id)
|
|
_ = p
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) CreateSpeaker(tenantID string, in *Speaker) (*Speaker, error) {
|
|
if in == nil || strings.TrimSpace(in.Role) == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
sp := &Speaker{ID: id, TenantID: tenantID, Role: strings.TrimSpace(in.Role), Endpoint: in.Endpoint, MetaJSON: in.MetaJSON}
|
|
m.speakers[id] = sp
|
|
return sp, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateSpeaker(tenantID, id string, patch *SpeakerPatch) (*Speaker, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
sp, ok := m.speakers[id]
|
|
if !ok || sp.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if patch.Role != nil {
|
|
sp.Role = strings.TrimSpace(*patch.Role)
|
|
}
|
|
if patch.Endpoint != nil {
|
|
sp.Endpoint = *patch.Endpoint
|
|
}
|
|
if patch.MetaJSON != nil {
|
|
sp.MetaJSON = *patch.MetaJSON
|
|
}
|
|
return sp, nil
|
|
}
|
|
|
|
func (m *Memory) ListRevisionPrefixes(tenantID, revisionID string, cursor string, limit int) ([]PrefixRow, string, bool) {
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if _, err := m.getRevisionLocked(tenantID, revisionID); err != nil {
|
|
return nil, "", false
|
|
}
|
|
all := m.revPrefixes[revisionID]
|
|
off := 0
|
|
if cursor != "" {
|
|
if n, err := strconv.Atoi(cursor); err == nil && n >= 0 {
|
|
off = n
|
|
}
|
|
}
|
|
end := off + limit
|
|
next := ""
|
|
more := false
|
|
if end > len(all) {
|
|
end = len(all)
|
|
} else {
|
|
more = true
|
|
next = fmt.Sprintf("%d", end)
|
|
}
|
|
if off >= len(all) {
|
|
return nil, "", false
|
|
}
|
|
return all[off:end], next, more
|
|
}
|
|
|
|
func (m *Memory) ListGlobalSettings(tenantID string) (map[string]any, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.settings[tenantID] == nil {
|
|
return map[string]any{}, nil
|
|
}
|
|
out := make(map[string]any, len(m.settings[tenantID]))
|
|
for k, v := range m.settings[tenantID] {
|
|
out[k] = v
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) PatchGlobalSettings(tenantID string, patch map[string]any) error {
|
|
if patch == nil {
|
|
return nil
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return ErrTenantScope
|
|
}
|
|
if m.settings[tenantID] == nil {
|
|
m.settings[tenantID] = make(map[string]any)
|
|
}
|
|
for key, val := range patch {
|
|
if strings.TrimSpace(key) == "" {
|
|
continue
|
|
}
|
|
if val == nil {
|
|
delete(m.settings[tenantID], key)
|
|
continue
|
|
}
|
|
m.settings[tenantID][key] = val
|
|
}
|
|
return nil
|
|
}
|