package store import ( "context" "net/netip" "time" "steward/internal/model" ) const leaseCols = `ip, subnet_id, mac, client_id, hostname, state, start, end, interface, updated` // LoadLeases reads every lease. func (s *Store) LoadLeases(ctx context.Context) ([]*model.Lease, error) { rows, err := s.db.QueryContext(ctx, `SELECT `+leaseCols+` FROM leases`) if err != nil { return nil, err } defer rows.Close() var out []*model.Lease for rows.Next() { l := &model.Lease{} var ip, start, end, updated string if err := rows.Scan(&ip, &l.SubnetID, &l.MAC, &l.ClientID, &l.Hostname, &l.State, &start, &end, &l.Interface, &updated); err != nil { return nil, err } l.IP, _ = netip.ParseAddr(ip) l.Start, l.End, l.Updated = parseTS(start), parseTS(end), parseTS(updated) out = append(out, l) } return out, rows.Err() } // PutLease writes a lease (replacing the address's previous one). It // returns once the write is durable enough that a crash doesn't lose it: // the DHCP server answers the client only afterwards. func (s *Store) PutLease(ctx context.Context, l *model.Lease) error { return s.PutLeases(ctx, []*model.Lease{l}) } // PutLeases writes several leases in one transaction (imports). func (s *Store) PutLeases(ctx context.Context, ls []*model.Lease) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() for _, l := range ls { if _, err := tx.ExecContext(ctx, `INSERT INTO leases (`+leaseCols+`) VALUES (?,?,?,?,?,?,?,?,?,?) ON CONFLICT(ip) DO UPDATE SET subnet_id = excluded.subnet_id, mac = excluded.mac, client_id = excluded.client_id, hostname = excluded.hostname, state = excluded.state, start = excluded.start, end = excluded.end, interface = excluded.interface, updated = excluded.updated`, l.IP.String(), l.SubnetID, l.MAC, l.ClientID, l.Hostname, l.State, ts(l.Start), ts(l.End), l.Interface, ts(l.Updated)); err != nil { return err } } return tx.Commit() } // DeleteLease removes the lease of an address. func (s *Store) DeleteLease(ctx context.Context, ip netip.Addr) error { res, err := s.db.ExecContext(ctx, `DELETE FROM leases WHERE ip = ?`, ip.String()) if err != nil { return err } if n, _ := res.RowsAffected(); n == 0 { return ErrNotFound } return nil } // PruneLeases deletes leases that ended before a time and aren't active // (they are kept a while so returning clients get their old address). func (s *Store) PruneLeases(ctx context.Context, before time.Time) (int64, error) { res, err := s.db.ExecContext(ctx, `DELETE FROM leases WHERE end < ?`, ts(before)) if err != nil { return 0, err } return res.RowsAffected() }