139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
package leader
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/metazla/meta-core/internal/config"
|
|
)
|
|
|
|
// LeaderLockInfo contains all URLs and metadata for the leader
|
|
type LeaderLockInfo struct {
|
|
Hostname string `json:"hostname"`
|
|
BaseUrl string `json:"baseUrl"`
|
|
ApiUrl string `json:"apiUrl"` // meta-core API (port 9000)
|
|
RedisUrl string `json:"redisUrl"`
|
|
WebdavUrl string `json:"webdavUrl"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
PID int `json:"pid"`
|
|
}
|
|
|
|
// Role represents the current role of this instance
|
|
type Role string
|
|
|
|
const (
|
|
RoleUnknown Role = "unknown"
|
|
RoleLeader Role = "leader"
|
|
RoleFollower Role = "follower"
|
|
)
|
|
|
|
// RoleProvider provides role and leader info
|
|
// This interface is used by the API server to get role information
|
|
// without needing the full election logic (which is now in bash)
|
|
type RoleProvider interface {
|
|
Role() Role
|
|
LeaderInfo() *LeaderLockInfo
|
|
IsLeader() bool
|
|
}
|
|
|
|
// LocalRoleProvider provides role info for this instance
|
|
// It reads the role from environment and builds leader info locally
|
|
// This is used when running as the leader (started by leader-election.sh)
|
|
type LocalRoleProvider struct {
|
|
config *config.Config
|
|
role Role
|
|
leaderInfo *LeaderLockInfo
|
|
}
|
|
|
|
// NewLocalRoleProvider creates a RoleProvider for the local instance
|
|
// It reads META_CORE_ROLE from environment (set by leader-election.sh)
|
|
func NewLocalRoleProvider(cfg *config.Config) *LocalRoleProvider {
|
|
roleStr := os.Getenv("META_CORE_ROLE")
|
|
var role Role
|
|
switch roleStr {
|
|
case "leader":
|
|
role = RoleLeader
|
|
case "follower":
|
|
role = RoleFollower
|
|
default:
|
|
// Default to leader if not set (for backward compatibility)
|
|
role = RoleLeader
|
|
}
|
|
|
|
provider := &LocalRoleProvider{
|
|
config: cfg,
|
|
role: role,
|
|
}
|
|
|
|
// Build leader info (always, since we're the leader when running)
|
|
provider.leaderInfo = provider.buildLeaderInfo()
|
|
|
|
return provider
|
|
}
|
|
|
|
// Role returns the current role
|
|
func (p *LocalRoleProvider) Role() Role {
|
|
return p.role
|
|
}
|
|
|
|
// LeaderInfo returns the leader info
|
|
func (p *LocalRoleProvider) LeaderInfo() *LeaderLockInfo {
|
|
if p.leaderInfo == nil {
|
|
return nil
|
|
}
|
|
// Return a copy
|
|
info := *p.leaderInfo
|
|
// Update timestamp on each call
|
|
info.Timestamp = time.Now().UnixMilli()
|
|
return &info
|
|
}
|
|
|
|
// IsLeader returns true if this instance is the leader
|
|
func (p *LocalRoleProvider) IsLeader() bool {
|
|
return p.role == RoleLeader
|
|
}
|
|
|
|
// buildLeaderInfo creates leader info for this instance
|
|
func (p *LocalRoleProvider) buildLeaderInfo() *LeaderLockInfo {
|
|
ip := getLocalIP()
|
|
hostname, _ := os.Hostname()
|
|
|
|
// Use BaseURL if configured, otherwise construct from IP
|
|
baseUrl := p.config.BaseURL
|
|
if baseUrl == "" {
|
|
baseUrl = fmt.Sprintf("http://%s:%d", ip, p.config.APIPort)
|
|
}
|
|
|
|
return &LeaderLockInfo{
|
|
Hostname: hostname,
|
|
BaseUrl: baseUrl,
|
|
ApiUrl: fmt.Sprintf("http://%s:%d", ip, p.config.HTTPPort),
|
|
RedisUrl: fmt.Sprintf("redis://%s:%d", ip, p.config.RedisPort),
|
|
WebdavUrl: fmt.Sprintf("http://%s:%d/webdav", ip, p.config.APIPort),
|
|
Timestamp: time.Now().UnixMilli(),
|
|
PID: os.Getpid(),
|
|
}
|
|
}
|
|
|
|
// getLocalIP returns the local IP address
|
|
func getLocalIP() string {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
hostname, _ := os.Hostname()
|
|
return hostname
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
if ipnet.IP.To4() != nil {
|
|
return ipnet.IP.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
hostname, _ := os.Hostname()
|
|
return hostname
|
|
}
|