Changed DB objects and added migrations

This commit is contained in:
Juan Font 2023-05-01 14:49:31 +00:00
parent ae03f440ee
commit cf22604a4b
3 changed files with 429 additions and 425 deletions

96
db.go
View File

@ -43,49 +43,53 @@ func (h *Headscale) initDB() error {
_ = db.Migrator().RenameTable("namespaces", "users") _ = db.Migrator().RenameTable("namespaces", "users")
// the big rename from Machine to Node
_ = db.Migrator().RenameTable("machines", "nodes")
_ = db.Migrator().RenameColumn(&Route{}, "machine_id", "node_id")
err = db.AutoMigrate(&User{}) err = db.AutoMigrate(&User{})
if err != nil { if err != nil {
return err return err
} }
_ = db.Migrator().RenameColumn(&Machine{}, "namespace_id", "user_id") _ = db.Migrator().RenameColumn(&Node{}, "namespace_id", "user_id")
_ = db.Migrator().RenameColumn(&PreAuthKey{}, "namespace_id", "user_id") _ = db.Migrator().RenameColumn(&PreAuthKey{}, "namespace_id", "user_id")
_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses") _ = db.Migrator().RenameColumn(&Node{}, "ip_address", "ip_addresses")
_ = db.Migrator().RenameColumn(&Machine{}, "name", "hostname") _ = db.Migrator().RenameColumn(&Node{}, "name", "hostname")
// GivenName is used as the primary source of DNS names, make sure // GivenName is used as the primary source of DNS names, make sure
// the field is populated and normalized if it was not when the // the field is populated and normalized if it was not when the
// machine was registered. // node was registered.
_ = db.Migrator().RenameColumn(&Machine{}, "nickname", "given_name") _ = db.Migrator().RenameColumn(&Node{}, "nickname", "given_name")
// If the Machine table has a column for registered, // If the Node table has a column for registered,
// find all occourences of "false" and drop them. Then // find all occourences of "false" and drop them. Then
// remove the column. // remove the column.
if db.Migrator().HasColumn(&Machine{}, "registered") { if db.Migrator().HasColumn(&Node{}, "registered") {
log.Info(). log.Info().
Msg(`Database has legacy "registered" column in machine, removing...`) Msg(`Database has legacy "registered" column in node, removing...`)
machines := Machines{} nodes := Nodes{}
if err := h.db.Not("registered").Find(&machines).Error; err != nil { if err := h.db.Not("registered").Find(&nodes).Error; err != nil {
log.Error().Err(err).Msg("Error accessing db") log.Error().Err(err).Msg("Error accessing db")
} }
for _, machine := range machines { for _, node := range nodes {
log.Info(). log.Info().
Str("machine", machine.Hostname). Str("node", node.Hostname).
Str("machine_key", machine.MachineKey). Str("machine_key", node.MachineKey).
Msg("Deleting unregistered machine") Msg("Deleting unregistered node")
if err := h.db.Delete(&Machine{}, machine.ID).Error; err != nil { if err := h.db.Delete(&Node{}, node.ID).Error; err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Str("machine", machine.Hostname). Str("node", node.Hostname).
Str("machine_key", machine.MachineKey). Str("machine_key", node.MachineKey).
Msg("Error deleting unregistered machine") Msg("Error deleting unregistered node")
} }
} }
err := db.Migrator().DropColumn(&Machine{}, "registered") err := db.Migrator().DropColumn(&Node{}, "registered")
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error dropping registered column") log.Error().Err(err).Msg("Error dropping registered column")
} }
@ -96,21 +100,21 @@ func (h *Headscale) initDB() error {
return err return err
} }
if db.Migrator().HasColumn(&Machine{}, "enabled_routes") { if db.Migrator().HasColumn(&Node{}, "enabled_routes") {
log.Info().Msgf("Database has legacy enabled_routes column in machine, migrating...") log.Info().Msgf("Database has legacy enabled_routes column in node, migrating...")
type MachineAux struct { type NodeAux struct {
ID uint64 ID uint64
EnabledRoutes IPPrefixes EnabledRoutes IPPrefixes
} }
machinesAux := []MachineAux{} nodesAux := []NodeAux{}
err := db.Table("machines").Select("id, enabled_routes").Scan(&machinesAux).Error err := db.Table("nodes").Select("id, enabled_routes").Scan(&nodesAux).Error
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error accessing db") log.Fatal().Err(err).Msg("Error accessing db")
} }
for _, machine := range machinesAux { for _, node := range nodesAux {
for _, prefix := range machine.EnabledRoutes { for _, prefix := range node.EnabledRoutes {
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
@ -120,8 +124,8 @@ func (h *Headscale) initDB() error {
continue continue
} }
err = db.Preload("Machine"). err = db.Preload("Node").
Where("machine_id = ? AND prefix = ?", machine.ID, IPPrefix(prefix)). Where("node_id = ? AND prefix = ?", node.ID, IPPrefix(prefix)).
First(&Route{}). First(&Route{}).
Error Error
if err == nil { if err == nil {
@ -133,7 +137,7 @@ func (h *Headscale) initDB() error {
} }
route := Route{ route := Route{
MachineID: machine.ID, NodeID: node.ID,
Advertised: true, Advertised: true,
Enabled: true, Enabled: true,
Prefix: IPPrefix(prefix), Prefix: IPPrefix(prefix),
@ -142,51 +146,51 @@ func (h *Headscale) initDB() error {
log.Error().Err(err).Msg("Error creating route") log.Error().Err(err).Msg("Error creating route")
} else { } else {
log.Info(). log.Info().
Uint64("machine_id", route.MachineID). Uint64("node_id", route.NodeID).
Str("prefix", prefix.String()). Str("prefix", prefix.String()).
Msg("Route migrated") Msg("Route migrated")
} }
} }
} }
err = db.Migrator().DropColumn(&Machine{}, "enabled_routes") err = db.Migrator().DropColumn(&Node{}, "enabled_routes")
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error dropping enabled_routes column") log.Error().Err(err).Msg("Error dropping enabled_routes column")
} }
} }
err = db.AutoMigrate(&Machine{}) err = db.AutoMigrate(&Node{})
if err != nil { if err != nil {
return err return err
} }
if db.Migrator().HasColumn(&Machine{}, "given_name") { if db.Migrator().HasColumn(&Node{}, "given_name") {
machines := Machines{} nodes := Nodes{}
if err := h.db.Find(&machines).Error; err != nil { if err := h.db.Find(&nodes).Error; err != nil {
log.Error().Err(err).Msg("Error accessing db") log.Error().Err(err).Msg("Error accessing db")
} }
for item, machine := range machines { for item, node := range nodes {
if machine.GivenName == "" { if node.GivenName == "" {
normalizedHostname, err := NormalizeToFQDNRules( normalizedHostname, err := NormalizeToFQDNRules(
machine.Hostname, node.Hostname,
h.cfg.OIDC.StripEmaildomain, h.cfg.OIDC.StripEmaildomain,
) )
if err != nil { if err != nil {
log.Error(). log.Error().
Caller(). Caller().
Str("hostname", machine.Hostname). Str("hostname", node.Hostname).
Err(err). Err(err).
Msg("Failed to normalize machine hostname in DB migration") Msg("Failed to normalize node hostname in DB migration")
} }
err = h.RenameMachine(&machines[item], normalizedHostname) err = h.RenameNode(&nodes[item], normalizedHostname)
if err != nil { if err != nil {
log.Error(). log.Error().
Caller(). Caller().
Str("hostname", machine.Hostname). Str("hostname", node.Hostname).
Err(err). Err(err).
Msg("Failed to save normalized machine name in DB migration") Msg("Failed to save normalized node name in DB migration")
} }
} }
} }
@ -324,7 +328,7 @@ func (hi *HostInfo) Scan(destination interface{}) error {
return json.Unmarshal([]byte(value), hi) return json.Unmarshal([]byte(value), hi)
default: default:
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination) return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
} }
} }
@ -370,7 +374,7 @@ func (i *IPPrefixes) Scan(destination interface{}) error {
return json.Unmarshal([]byte(value), i) return json.Unmarshal([]byte(value), i)
default: default:
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination) return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
} }
} }
@ -392,7 +396,7 @@ func (i *StringList) Scan(destination interface{}) error {
return json.Unmarshal([]byte(value), i) return json.Unmarshal([]byte(value), i)
default: default:
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination) return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
} }
} }

680
node.go

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,9 @@ var (
type Route struct { type Route struct {
gorm.Model gorm.Model
MachineID uint64 NodeID uint64
Machine Machine Node Node
Prefix IPPrefix Prefix IPPrefix
Advertised bool Advertised bool
Enabled bool Enabled bool
@ -35,7 +35,7 @@ type Route struct {
type Routes []Route type Routes []Route
func (r *Route) String() string { func (r *Route) String() string {
return fmt.Sprintf("%s:%s", r.Machine, netip.Prefix(r.Prefix).String()) return fmt.Sprintf("%s:%s", r.Node, netip.Prefix(r.Prefix).String())
} }
func (r *Route) isExitRoute() bool { func (r *Route) isExitRoute() bool {
@ -53,7 +53,7 @@ func (rs Routes) toPrefixes() []netip.Prefix {
func (h *Headscale) GetRoutes() ([]Route, error) { func (h *Headscale) GetRoutes() ([]Route, error) {
var routes []Route var routes []Route
err := h.db.Preload("Machine").Find(&routes).Error err := h.db.Preload("Node").Find(&routes).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -61,11 +61,11 @@ func (h *Headscale) GetRoutes() ([]Route, error) {
return routes, nil return routes, nil
} }
func (h *Headscale) GetMachineRoutes(m *Machine) ([]Route, error) { func (h *Headscale) GetNodeRoutes(m *Node) ([]Route, error) {
var routes []Route var routes []Route
err := h.db. err := h.db.
Preload("Machine"). Preload("Node").
Where("machine_id = ?", m.ID). Where("node_id = ?", m.ID).
Find(&routes).Error Find(&routes).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err return nil, err
@ -76,7 +76,7 @@ func (h *Headscale) GetMachineRoutes(m *Machine) ([]Route, error) {
func (h *Headscale) GetRoute(id uint64) (*Route, error) { func (h *Headscale) GetRoute(id uint64) (*Route, error) {
var route Route var route Route
err := h.db.Preload("Machine").First(&route, id).Error err := h.db.Preload("Node").First(&route, id).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,10 +94,10 @@ func (h *Headscale) EnableRoute(id uint64) error {
// be enabled at the same time, as per // be enabled at the same time, as per
// https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002 // https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002
if route.isExitRoute() { if route.isExitRoute() {
return h.enableRoutes(&route.Machine, ExitRouteV4.String(), ExitRouteV6.String()) return h.enableRoutes(&route.Node, ExitRouteV4.String(), ExitRouteV6.String())
} }
return h.enableRoutes(&route.Machine, netip.Prefix(route.Prefix).String()) return h.enableRoutes(&route.Node, netip.Prefix(route.Prefix).String())
} }
func (h *Headscale) DisableRoute(id uint64) error { func (h *Headscale) DisableRoute(id uint64) error {
@ -129,8 +129,8 @@ func (h *Headscale) DeleteRoute(id uint64) error {
return h.handlePrimarySubnetFailover() return h.handlePrimarySubnetFailover()
} }
func (h *Headscale) DeleteMachineRoutes(m *Machine) error { func (h *Headscale) DeleteNodeRoutes(node *Node) error {
routes, err := h.GetMachineRoutes(m) routes, err := h.GetNodeRoutes(node)
if err != nil { if err != nil {
return err return err
} }
@ -144,14 +144,14 @@ func (h *Headscale) DeleteMachineRoutes(m *Machine) error {
return h.handlePrimarySubnetFailover() return h.handlePrimarySubnetFailover()
} }
// isUniquePrefix returns if there is another machine providing the same route already. // isUniquePrefix returns if there is another node providing the same route already.
func (h *Headscale) isUniquePrefix(route Route) bool { func (h *Headscale) isUniquePrefix(route Route) bool {
var count int64 var count int64
h.db. h.db.
Model(&Route{}). Model(&Route{}).
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?", Where("prefix = ? AND node_id != ? AND advertised = ? AND enabled = ?",
route.Prefix, route.Prefix,
route.MachineID, route.NodeID,
true, true).Count(&count) true, true).Count(&count)
return count == 0 return count == 0
@ -160,7 +160,7 @@ func (h *Headscale) isUniquePrefix(route Route) bool {
func (h *Headscale) getPrimaryRoute(prefix netip.Prefix) (*Route, error) { func (h *Headscale) getPrimaryRoute(prefix netip.Prefix) (*Route, error) {
var route Route var route Route
err := h.db. err := h.db.
Preload("Machine"). Preload("Node").
Where("prefix = ? AND advertised = ? AND enabled = ? AND is_primary = ?", IPPrefix(prefix), true, true, true). Where("prefix = ? AND advertised = ? AND enabled = ? AND is_primary = ?", IPPrefix(prefix), true, true, true).
First(&route).Error First(&route).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@ -174,13 +174,13 @@ func (h *Headscale) getPrimaryRoute(prefix netip.Prefix) (*Route, error) {
return &route, nil return &route, nil
} }
// getMachinePrimaryRoutes returns the routes that are enabled and marked as primary (for subnet failover) // getNodePrimaryRoutes returns the routes that are enabled and marked as primary (for subnet failover)
// Exit nodes are not considered for this, as they are never marked as Primary. // Exit nodes are not considered for this, as they are never marked as Primary.
func (h *Headscale) getMachinePrimaryRoutes(m *Machine) ([]Route, error) { func (h *Headscale) getNodePrimaryRoutes(m *Node) ([]Route, error) {
var routes []Route var routes []Route
err := h.db. err := h.db.
Preload("Machine"). Preload("Node").
Where("machine_id = ? AND advertised = ? AND enabled = ? AND is_primary = ?", m.ID, true, true, true). Where("node_id = ? AND advertised = ? AND enabled = ? AND is_primary = ?", m.ID, true, true, true).
Find(&routes).Error Find(&routes).Error
if err != nil { if err != nil {
return nil, err return nil, err
@ -189,15 +189,15 @@ func (h *Headscale) getMachinePrimaryRoutes(m *Machine) ([]Route, error) {
return routes, nil return routes, nil
} }
func (h *Headscale) processMachineRoutes(machine *Machine) error { func (h *Headscale) processNodeRoutes(node *Node) error {
currentRoutes := []Route{} currentRoutes := []Route{}
err := h.db.Where("machine_id = ?", machine.ID).Find(&currentRoutes).Error err := h.db.Where("node_id = ?", node.ID).Find(&currentRoutes).Error
if err != nil { if err != nil {
return err return err
} }
advertisedRoutes := map[netip.Prefix]bool{} advertisedRoutes := map[netip.Prefix]bool{}
for _, prefix := range machine.HostInfo.RoutableIPs { for _, prefix := range node.HostInfo.RoutableIPs {
advertisedRoutes[prefix] = false advertisedRoutes[prefix] = false
} }
@ -224,7 +224,7 @@ func (h *Headscale) processMachineRoutes(machine *Machine) error {
for prefix, exists := range advertisedRoutes { for prefix, exists := range advertisedRoutes {
if !exists { if !exists {
route := Route{ route := Route{
MachineID: machine.ID, NodeID: node.ID,
Prefix: IPPrefix(prefix), Prefix: IPPrefix(prefix),
Advertised: true, Advertised: true,
Enabled: false, Enabled: false,
@ -243,7 +243,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
// first, get all the enabled routes // first, get all the enabled routes
var routes []Route var routes []Route
err := h.db. err := h.db.
Preload("Machine"). Preload("Node").
Where("advertised = ? AND enabled = ?", true, true). Where("advertised = ? AND enabled = ?", true, true).
Find(&routes).Error Find(&routes).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@ -261,7 +261,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
if h.isUniquePrefix(route) || errors.Is(err, gorm.ErrRecordNotFound) { if h.isUniquePrefix(route) || errors.Is(err, gorm.ErrRecordNotFound) {
log.Info(). log.Info().
Str("prefix", netip.Prefix(route.Prefix).String()). Str("prefix", netip.Prefix(route.Prefix).String()).
Str("machine", route.Machine.GivenName). Str("node", route.Node.GivenName).
Msg("Setting primary route") Msg("Setting primary route")
routes[pos].IsPrimary = true routes[pos].IsPrimary = true
err := h.db.Save(&routes[pos]).Error err := h.db.Save(&routes[pos]).Error
@ -278,23 +278,23 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
} }
if route.IsPrimary { if route.IsPrimary {
if route.Machine.isOnline() { if route.Node.isOnline() {
continue continue
} }
// machine offline, find a new primary // node offline, find a new primary
log.Info(). log.Info().
Str("machine", route.Machine.Hostname). Str("node", route.Node.Hostname).
Str("prefix", netip.Prefix(route.Prefix).String()). Str("prefix", netip.Prefix(route.Prefix).String()).
Msgf("machine offline, finding a new primary subnet") Msgf("node offline, finding a new primary subnet")
// find a new primary route // find a new primary route
var newPrimaryRoutes []Route var newPrimaryRoutes []Route
err := h.db. err := h.db.
Preload("Machine"). Preload("Node").
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?", Where("prefix = ? AND node_id != ? AND advertised = ? AND enabled = ?",
route.Prefix, route.Prefix,
route.MachineID, route.NodeID,
true, true). true, true).
Find(&newPrimaryRoutes).Error Find(&newPrimaryRoutes).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@ -305,7 +305,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
var newPrimaryRoute *Route var newPrimaryRoute *Route
for pos, r := range newPrimaryRoutes { for pos, r := range newPrimaryRoutes {
if r.Machine.isOnline() { if r.Node.isOnline() {
newPrimaryRoute = &newPrimaryRoutes[pos] newPrimaryRoute = &newPrimaryRoutes[pos]
break break
@ -314,7 +314,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
if newPrimaryRoute == nil { if newPrimaryRoute == nil {
log.Warn(). log.Warn().
Str("machine", route.Machine.Hostname). Str("node", route.Node.Hostname).
Str("prefix", netip.Prefix(route.Prefix).String()). Str("prefix", netip.Prefix(route.Prefix).String()).
Msgf("no alternative primary route found") Msgf("no alternative primary route found")
@ -322,9 +322,9 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
} }
log.Info(). log.Info().
Str("old_machine", route.Machine.Hostname). Str("old_node", route.Node.Hostname).
Str("prefix", netip.Prefix(route.Prefix).String()). Str("prefix", netip.Prefix(route.Prefix).String()).
Str("new_machine", newPrimaryRoute.Machine.Hostname). Str("new_node", newPrimaryRoute.Node.Hostname).
Msgf("found new primary route") Msgf("found new primary route")
// disable the old primary route // disable the old primary route
@ -362,7 +362,7 @@ func (rs Routes) toProto() []*v1.Route {
for _, route := range rs { for _, route := range rs {
protoRoute := v1.Route{ protoRoute := v1.Route{
Id: uint64(route.ID), Id: uint64(route.ID),
Machine: route.Machine.toProto(), Node: route.Node.toProto(),
Prefix: netip.Prefix(route.Prefix).String(), Prefix: netip.Prefix(route.Prefix).String(),
Advertised: route.Advertised, Advertised: route.Advertised,
Enabled: route.Enabled, Enabled: route.Enabled,