mirror of
https://github.com/beclab/Olares
synced 2026-04-21 13:37:46 +00:00
* cli, daemon: add `olares-cli status` backed by olaresd /system/status
Adds a new top-level `olares-cli status` command that calls the local
olaresd daemon's `/system/status` HTTP endpoint and prints either a
grouped, annotated human-readable report or raw JSON.
To avoid duplicating the response schema, the daemon's `state` struct
and related enums are extracted into a new shared package
`cli/pkg/daemon/state`. The daemon now type-aliases those types so all
existing daemon call sites keep compiling unchanged.
Made-with: Cursor
* cli: drop unused state.APIResponse envelope type
The HTTP client in cli/pkg/daemon/api/client.go intentionally uses an
inline anonymous envelope with json.RawMessage for the data field so
that --json mode can passthrough the bytes verbatim, so the public
APIResponse{Data State} type defined here had zero references. Remove
it; if a strongly-typed consumer ever shows up, re-add then.
Addresses Cursor Bugbot feedback on PR #2917.
Made-with: Cursor
189 lines
4.5 KiB
Go
189 lines
4.5 KiB
Go
package changeip
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/beclab/Olares/daemon/pkg/cli"
|
|
"github.com/beclab/Olares/daemon/pkg/cluster/state"
|
|
"github.com/beclab/Olares/daemon/pkg/commands"
|
|
"github.com/beclab/Olares/daemon/pkg/utils"
|
|
"github.com/nxadm/tail"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
type changeIp struct {
|
|
commands.Operation
|
|
isRetryChange bool
|
|
notInstalled bool
|
|
logFile string
|
|
}
|
|
|
|
var _ commands.Interface = &changeIp{}
|
|
|
|
func New() commands.Interface {
|
|
return &changeIp{
|
|
Operation: commands.Operation{
|
|
Name: commands.ChangeIp,
|
|
},
|
|
logFile: commands.COMMAND_BASE_DIR + "/logs/changeip.log",
|
|
}
|
|
}
|
|
|
|
func (i *changeIp) Execute(ctx context.Context, p any) (res any, err error) {
|
|
i.isRetryChange = state.CurrentState.TerminusState == state.IPChangeFailed
|
|
|
|
cmd := commands.NewBaseCommand()
|
|
cmd.WithDir_(commands.COMMAND_BASE_DIR).
|
|
WithPid_(commands.CHANGINGIP_PID_FILE).
|
|
AddEnv_("QUIET", "1").
|
|
WithWatchDog_(i.watch)
|
|
|
|
if _, err := os.Stat(commands.INSTALL_LOCK); err != nil && os.IsNotExist(err) {
|
|
cmd.AddEnv_("NOT_INSTALLED", "1")
|
|
i.notInstalled = true
|
|
}
|
|
|
|
if _, err := os.Stat(commands.PREPARE_LOCK); err != nil && os.IsNotExist(err) {
|
|
cmd.AddEnv_("NOT_PREPARED", "1")
|
|
}
|
|
|
|
if _, err := os.Stat(i.logFile); err == nil {
|
|
if err = os.Remove(i.logFile); err != nil {
|
|
klog.Error("remove prev log file error, ", err)
|
|
}
|
|
}
|
|
|
|
var cmds []string = []string{
|
|
"change-ip",
|
|
"--version", commands.INSTALLED_VERSION,
|
|
"--base-dir", commands.TERMINUS_BASE_DIR,
|
|
}
|
|
|
|
// FIXME: maybe we can always get master node ip from etcd config or anything else.
|
|
masterIp, err := utils.MasterNodeIp(!i.notInstalled)
|
|
if err != nil {
|
|
klog.Error("get master node ip error,", err)
|
|
}
|
|
|
|
// backup prev ip, if change failed we can resume with this ip
|
|
err = os.WriteFile(commands.PREV_IP_TO_CHANGE_FILE, []byte(masterIp), 0644)
|
|
if err != nil {
|
|
klog.Error("cannot backup prev ip, ", err)
|
|
}
|
|
|
|
state.ChangeTerminusStateTo(state.IPChanging)
|
|
|
|
// remove PREV_IP_CHANGE_FAILED tag file if exists
|
|
if _, err = os.Stat(commands.PREV_IP_CHANGE_FAILED); err == nil {
|
|
if err = os.Remove(commands.PREV_IP_CHANGE_FAILED); err != nil {
|
|
klog.Warning("remove ip change failed tag file error, ", err)
|
|
}
|
|
}
|
|
|
|
if err = cmd.RunAsync_(ctx, cli.TERMINUS_CLI, cmds...); err != nil {
|
|
i.setFailedState()
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func (i *changeIp) watch(ctx context.Context) {
|
|
go func() {
|
|
// delay starting to watch
|
|
time.Sleep(10 * time.Second)
|
|
|
|
ticker := time.NewTicker(2 * time.Second)
|
|
defer ticker.Stop()
|
|
successState := state.TerminusRunning
|
|
if i.notInstalled {
|
|
successState = state.NotInstalled
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
klog.Info("change ip command finished")
|
|
_, err := os.Stat(commands.CHANGINGIP_PID_FILE)
|
|
if os.IsNotExist(err) {
|
|
// double check
|
|
if i.tailLog() {
|
|
klog.Info("change ip command finished, change state")
|
|
state.ChangeTerminusStateTo(successState)
|
|
return
|
|
} else {
|
|
klog.Warning("check log file, process not succeed")
|
|
i.setFailedState()
|
|
return
|
|
}
|
|
}
|
|
klog.Warning("change ip killed")
|
|
state.ChangeTerminusStateTo(state.SystemError)
|
|
return
|
|
case <-ticker.C:
|
|
if i.tailLog() {
|
|
klog.Info("watch log succeed")
|
|
state.ChangeTerminusStateTo(successState)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
}
|
|
|
|
func (i *changeIp) tailLog() (finished bool) {
|
|
tailFile := i.logFile
|
|
|
|
info, err := os.Stat(tailFile)
|
|
if err != nil {
|
|
klog.Error(err)
|
|
return false
|
|
}
|
|
|
|
filesize := info.Size()
|
|
tailsize := min(filesize, 512)
|
|
|
|
t, err := tail.TailFile(tailFile,
|
|
tail.Config{Follow: false, Location: &tail.SeekInfo{Offset: -tailsize, Whence: io.SeekEnd}})
|
|
defer t.Stop()
|
|
if err != nil {
|
|
klog.Error("tail log error, ", err)
|
|
return false
|
|
}
|
|
|
|
keyWords := "Olares OS components execute successfully!"
|
|
var line *tail.Line
|
|
for line = range t.Lines {
|
|
if strings.Contains(line.Text, keyWords) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if line != nil {
|
|
// for debug
|
|
klog.Info(line.Text)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (i *changeIp) setFailedState() {
|
|
if i.isRetryChange {
|
|
klog.Error("retry ip change failed")
|
|
state.ChangeTerminusStateTo(state.SystemError)
|
|
} else {
|
|
// create a ip change failed tag file, make the ip-change command can
|
|
// be resumed if the device get reboot
|
|
err := os.WriteFile(commands.PREV_IP_CHANGE_FAILED, []byte(time.Now().String()), 0644)
|
|
if err != nil {
|
|
klog.Error("write ip change failed tag file error, ", err)
|
|
}
|
|
|
|
state.ChangeTerminusStateTo(state.IPChangeFailed)
|
|
}
|
|
}
|