github.com/grafana/synthetic-monitoring-agent@v0.64.2/cmd/test-api/internal/db/db.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"slices"
     7  	"sync"
     8  	"time"
     9  
    10  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    11  )
    12  
    13  type Db struct {
    14  	mutex         sync.Mutex
    15  	tenants       map[int64]sm.Tenant
    16  	probes        map[int64]sm.Probe
    17  	probeTokens   map[string]int64
    18  	checks        map[int64]sm.Check
    19  	checksByProbe map[int64][]int64
    20  }
    21  
    22  func New() *Db {
    23  	return &Db{
    24  		tenants:       make(map[int64]sm.Tenant),
    25  		probes:        make(map[int64]sm.Probe),
    26  		probeTokens:   make(map[string]int64),
    27  		checks:        make(map[int64]sm.Check),
    28  		checksByProbe: make(map[int64][]int64),
    29  	}
    30  }
    31  
    32  func (db *Db) FindProbeIDByToken(ctx context.Context, token []byte) (int64, error) {
    33  	db.mutex.Lock()
    34  	defer db.mutex.Unlock()
    35  
    36  	if id, found := db.probeTokens[string(token)]; found {
    37  		return id, nil
    38  	}
    39  
    40  	return -1, errors.New("token not found")
    41  }
    42  
    43  func (db *Db) FindProbeByID(ctx context.Context, id int64) (*sm.Probe, error) {
    44  	db.mutex.Lock()
    45  	defer db.mutex.Unlock()
    46  
    47  	if probe, found := db.probes[id]; found {
    48  		return &probe, nil
    49  	}
    50  
    51  	return nil, errors.New("probe not found")
    52  }
    53  
    54  func (db *Db) ListChecksForProbe(ctx context.Context, id int64) ([]sm.Check, error) {
    55  	db.mutex.Lock()
    56  	defer db.mutex.Unlock()
    57  
    58  	checks := make([]sm.Check, 0, len(db.checksByProbe[id]))
    59  
    60  	for _, checkId := range db.checksByProbe[id] {
    61  		checks = append(checks, db.checks[checkId])
    62  	}
    63  
    64  	return checks, nil
    65  }
    66  
    67  func (db *Db) AddTenant(ctx context.Context, tenant *sm.Tenant) error {
    68  	db.mutex.Lock()
    69  	defer db.mutex.Unlock()
    70  
    71  	for _, t := range db.tenants {
    72  		if t.StackId == tenant.StackId {
    73  			return errors.New("tenant already exists")
    74  		}
    75  	}
    76  
    77  	now := nsNow()
    78  
    79  	newTenant := sm.Tenant{
    80  		Id:       int64(len(db.tenants) + 1),
    81  		OrgId:    tenant.OrgId,
    82  		StackId:  tenant.StackId,
    83  		Status:   sm.TenantStatus_ACTIVE,
    84  		Reason:   "",
    85  		Created:  now,
    86  		Modified: now,
    87  	}
    88  	if tenant.MetricsRemote != nil {
    89  		newTenant.MetricsRemote = &sm.RemoteInfo{
    90  			Name:     tenant.MetricsRemote.Name,
    91  			Url:      tenant.MetricsRemote.Url,
    92  			Username: tenant.MetricsRemote.Username,
    93  			Password: tenant.MetricsRemote.Password,
    94  		}
    95  	}
    96  
    97  	if tenant.EventsRemote != nil {
    98  		newTenant.EventsRemote = &sm.RemoteInfo{
    99  			Name:     tenant.EventsRemote.Name,
   100  			Url:      tenant.EventsRemote.Url,
   101  			Username: tenant.EventsRemote.Username,
   102  			Password: tenant.EventsRemote.Password,
   103  		}
   104  	}
   105  
   106  	db.tenants[newTenant.Id] = newTenant
   107  
   108  	*tenant = newTenant
   109  
   110  	return nil
   111  }
   112  
   113  func (db *Db) GetTenant(ctx context.Context, id int64) (*sm.Tenant, error) {
   114  	db.mutex.Lock()
   115  	defer db.mutex.Unlock()
   116  
   117  	tenant, found := db.tenants[id]
   118  	if !found {
   119  		return nil, errors.New("tenant not found")
   120  	}
   121  
   122  	return &tenant, nil
   123  }
   124  
   125  func (db *Db) ListTenants(ctx context.Context) ([]sm.Tenant, error) {
   126  	db.mutex.Lock()
   127  	defer db.mutex.Unlock()
   128  
   129  	tenants := make([]sm.Tenant, 0, len(db.tenants))
   130  	for _, tenant := range db.tenants {
   131  		tenants = append(tenants, tenant)
   132  	}
   133  
   134  	return tenants, nil
   135  }
   136  
   137  func (db *Db) UpdateTenant(ctx context.Context, tenant *sm.Tenant) (*sm.Tenant, error) {
   138  	db.mutex.Lock()
   139  	defer db.mutex.Unlock()
   140  
   141  	if _, found := db.tenants[tenant.Id]; !found {
   142  		return nil, errors.New("tenant not found")
   143  	}
   144  
   145  	tenant.Modified = nsNow()
   146  	db.tenants[tenant.Id] = *tenant
   147  
   148  	return tenant, nil
   149  }
   150  
   151  func (db *Db) DeleteTenant(ctx context.Context, id int64) error {
   152  	db.mutex.Lock()
   153  	defer db.mutex.Unlock()
   154  
   155  	if _, found := db.tenants[id]; !found {
   156  		return errors.New("tenant not found")
   157  	}
   158  
   159  	for _, probe := range db.probes {
   160  		if probe.TenantId == id {
   161  			return errors.New("tenant is still referenced by probe")
   162  		}
   163  	}
   164  
   165  	for _, check := range db.checks {
   166  		if check.TenantId == id {
   167  			return errors.New("tenant is still referenced by check")
   168  		}
   169  	}
   170  
   171  	delete(db.tenants, id)
   172  
   173  	return nil
   174  }
   175  
   176  func (db *Db) AddProbe(ctx context.Context, probe *sm.Probe, token []byte) error {
   177  	db.mutex.Lock()
   178  	defer db.mutex.Unlock()
   179  
   180  	if err := probe.Validate(); err != nil {
   181  		return err
   182  	}
   183  
   184  	if _, found := db.tenants[probe.TenantId]; !found {
   185  		return errors.New("invalid tenant")
   186  	}
   187  
   188  	for _, p := range db.probes {
   189  		// if the new probe is public, it cannot use an existing name in any of the probes
   190  		// if the existing probe is public, the new probe cannot use its name
   191  		// if they are private and both are in the same tenant, they cannot have the same name
   192  		if (probe.Public || p.TenantId == probe.TenantId || p.Public) && p.Name == probe.Name {
   193  			return errors.New("probe already exists")
   194  		}
   195  	}
   196  
   197  	if _, found := db.probeTokens[string(token)]; found {
   198  		return errors.New("invalid probe token")
   199  	}
   200  
   201  	probe.Id = int64(len(db.probes) + 1)
   202  	probe.Created = nsNow()
   203  	probe.Modified = probe.Created
   204  
   205  	db.probes[probe.Id] = *probe
   206  	db.probeTokens[string(token)] = probe.Id
   207  
   208  	return nil
   209  }
   210  
   211  func (db *Db) GetProbe(ctx context.Context, id int64) (*sm.Probe, error) {
   212  	db.mutex.Lock()
   213  	defer db.mutex.Unlock()
   214  
   215  	if probe, found := db.probes[id]; found {
   216  		return &probe, nil
   217  	}
   218  
   219  	return nil, errors.New("probe not found")
   220  }
   221  
   222  func (db *Db) ListProbes(ctx context.Context) ([]sm.Probe, error) {
   223  	db.mutex.Lock()
   224  	defer db.mutex.Unlock()
   225  
   226  	probes := make([]sm.Probe, 0, len(db.probes))
   227  
   228  	for _, probe := range db.probes {
   229  		probes = append(probes, probe)
   230  	}
   231  
   232  	return probes, nil
   233  }
   234  
   235  func (db *Db) UpdateProbe(ctx context.Context, probe *sm.Probe) (*sm.Probe, error) {
   236  	db.mutex.Lock()
   237  	defer db.mutex.Unlock()
   238  
   239  	oldProbe, found := db.probes[probe.Id]
   240  	if !found {
   241  		return nil, errors.New("probe not found")
   242  	}
   243  
   244  	if oldProbe.Name != probe.Name {
   245  		return nil, errors.New("cannot rename probes")
   246  	}
   247  
   248  	if oldProbe.TenantId != probe.TenantId {
   249  		return nil, errors.New("cannot move probes between tenants")
   250  	}
   251  
   252  	if err := probe.Validate(); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	for _, p := range db.probes {
   257  		if p.Id == probe.Id {
   258  			continue
   259  		}
   260  
   261  		// If the upated probe is public (or becoming public), it
   262  		// cannot share the name with any other probe.
   263  		if probe.Public && p.Name == probe.Name {
   264  			return nil, errors.New("invalid probe name")
   265  		}
   266  	}
   267  
   268  	updatedProbe := *probe
   269  	updatedProbe.Modified = nsNow()
   270  
   271  	db.probes[probe.Id] = updatedProbe
   272  
   273  	return &updatedProbe, nil
   274  }
   275  
   276  func (db *Db) DeleteProbe(ctx context.Context, id int64) error {
   277  	db.mutex.Lock()
   278  	defer db.mutex.Unlock()
   279  
   280  	if _, found := db.probes[id]; !found {
   281  		return errors.New("probe not found")
   282  	}
   283  
   284  	for _, check := range db.checks {
   285  		if slices.Contains(check.Probes, id) {
   286  			return errors.New("probe is still referenced by check")
   287  		}
   288  	}
   289  
   290  	for token, probeId := range db.probeTokens {
   291  		if probeId == id {
   292  			delete(db.probeTokens, token)
   293  		}
   294  	}
   295  
   296  	delete(db.probes, id)
   297  
   298  	return nil
   299  }
   300  
   301  func (db *Db) AddCheck(ctx context.Context, check *sm.Check) error {
   302  	db.mutex.Lock()
   303  	defer db.mutex.Unlock()
   304  
   305  	if err := check.Validate(); err != nil {
   306  		return err
   307  	}
   308  
   309  	if _, found := db.tenants[check.TenantId]; !found {
   310  		return errors.New("invalid tenant")
   311  	}
   312  
   313  	for _, c := range db.checks {
   314  		if c.Target == check.Target && c.Job == check.Job {
   315  			return errors.New("check already exists")
   316  		}
   317  	}
   318  
   319  	for _, pId := range check.Probes {
   320  		probe, found := db.probes[pId]
   321  		if !found {
   322  			return errors.New("probe not found")
   323  		}
   324  
   325  		if probe.TenantId != check.TenantId && !probe.Public {
   326  			return errors.New("invalid probe")
   327  		}
   328  	}
   329  
   330  	check.Id = int64(len(db.checks) + 1)
   331  	check.Created = nsNow()
   332  	check.Modified = check.Created
   333  
   334  	db.checks[check.Id] = *check
   335  
   336  	// now that we now everything is valid, add checks to probes
   337  	for _, pId := range check.Probes {
   338  		db.checksByProbe[pId] = append(db.checksByProbe[pId], check.Id)
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (db *Db) GetCheck(ctx context.Context, id int64) (*sm.Check, error) {
   345  	db.mutex.Lock()
   346  	defer db.mutex.Unlock()
   347  
   348  	if check, found := db.checks[id]; found {
   349  		return &check, nil
   350  	}
   351  
   352  	return nil, errors.New("check not found")
   353  }
   354  
   355  func (db *Db) ListChecks(ctx context.Context) ([]sm.Check, error) {
   356  	db.mutex.Lock()
   357  	defer db.mutex.Unlock()
   358  
   359  	checks := make([]sm.Check, 0, len(db.checks))
   360  
   361  	for _, check := range db.checks {
   362  		checks = append(checks, check)
   363  	}
   364  
   365  	return checks, nil
   366  }
   367  
   368  func (db *Db) UpdateCheck(ctx context.Context, check *sm.Check) (*sm.Check, error) {
   369  	db.mutex.Lock()
   370  	defer db.mutex.Unlock()
   371  
   372  	oldCheck, found := db.checks[check.Id]
   373  	if !found {
   374  		return nil, errors.New("check not found")
   375  	}
   376  
   377  	if oldCheck.Job != check.Job {
   378  		return nil, errors.New("cannot change job")
   379  	}
   380  
   381  	if oldCheck.Target != check.Target {
   382  		return nil, errors.New("cannot change target")
   383  	}
   384  
   385  	if oldCheck.TenantId != check.TenantId {
   386  		return nil, errors.New("cannot move checks between tenants")
   387  	}
   388  
   389  	if err := check.Validate(); err != nil {
   390  		return nil, err
   391  	}
   392  
   393  	check.Modified = nsNow()
   394  
   395  	db.checks[check.Id] = *check
   396  
   397  	return &oldCheck, nil
   398  }
   399  
   400  func (db *Db) DeleteCheck(ctx context.Context, id int64) error {
   401  	db.mutex.Lock()
   402  	defer db.mutex.Unlock()
   403  
   404  	if _, found := db.checks[id]; !found {
   405  		return errors.New("check not found")
   406  	}
   407  
   408  	delete(db.checks, id)
   409  
   410  	return nil
   411  }
   412  
   413  func nsNow() float64 {
   414  	return float64(time.Now().UnixNano()) / 1e9
   415  }