799 lines
18 KiB
Go
799 lines
18 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),
|
|
Etag: in.Etag,
|
|
RefreshIntervalSec: in.RefreshIntervalSec,
|
|
CommunityID: in.CommunityID,
|
|
}
|
|
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.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
|
|
}
|
|
}
|
|
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 || (in.ASN == nil && (in.Prefix == 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 != "AS_PREFIXES" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
id := uuid.NewString()
|
|
e := &ASEntry{ID: id, ModuleID: moduleID, ASN: in.ASN, Prefix: in.Prefix, 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
|
|
}
|
|
if patch.ASN != nil {
|
|
e.ASN = patch.ASN
|
|
}
|
|
if patch.Prefix != nil {
|
|
p := strings.TrimSpace(*patch.Prefix)
|
|
if p == "" {
|
|
e.Prefix = nil
|
|
} else {
|
|
e.Prefix = &p
|
|
}
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
e.CommunityID = nil
|
|
} else {
|
|
e.CommunityID = &v
|
|
}
|
|
}
|
|
return e, 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.Kind) == "" {
|
|
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, Name: in.Name, Kind: strings.TrimSpace(in.Kind), 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.Name != nil {
|
|
c.Name = *patch.Name
|
|
}
|
|
if patch.Kind != nil {
|
|
c.Kind = strings.TrimSpace(*patch.Kind)
|
|
}
|
|
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 || strings.TrimSpace(in.Neighbor) == "" || in.RemoteASN == 0 {
|
|
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: strings.TrimSpace(in.Neighbor), RemoteASN: in.RemoteASN, Enabled: in.Enabled,
|
|
SessionState: in.SessionState, PoliciesJSON: in.PoliciesJSON,
|
|
}
|
|
if !p.Enabled && p.SessionState == "" {
|
|
p.Enabled = true
|
|
}
|
|
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 {
|
|
p.Neighbor = strings.TrimSpace(*patch.Neighbor)
|
|
}
|
|
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
|
|
}
|