github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/upgrade_manager.go (about)

     1  // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package nodes
     4  
     5  import (
     6  	"crypto/md5"
     7  	"fmt"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     9  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
    10  	"github.com/TeaOSLab/EdgeNode/internal/events"
    11  	"github.com/TeaOSLab/EdgeNode/internal/goman"
    12  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    13  	"github.com/TeaOSLab/EdgeNode/internal/rpc"
    14  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    15  	executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
    16  	"github.com/iwind/TeaGo/Tea"
    17  	stringutil "github.com/iwind/TeaGo/utils/string"
    18  	"github.com/iwind/gosock/pkg/gosock"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"time"
    23  )
    24  
    25  var sharedUpgradeManager = NewUpgradeManager()
    26  
    27  // UpgradeManager 节点升级管理器
    28  // TODO 需要在集群中设置是否自动更新
    29  type UpgradeManager struct {
    30  	isInstalling bool
    31  	lastFile     string
    32  	exe          string
    33  }
    34  
    35  // NewUpgradeManager 获取新对象
    36  func NewUpgradeManager() *UpgradeManager {
    37  	return &UpgradeManager{}
    38  }
    39  
    40  // Start 启动升级
    41  func (this *UpgradeManager) Start() {
    42  	// 必须放在文件解压之前读取可执行文件路径,防止解析之后,当前的可执行文件路径发生改变
    43  	exe, err := os.Executable()
    44  	if err != nil {
    45  		remotelogs.Error("UPGRADE_MANAGER", "can not find current executable file name")
    46  		return
    47  	}
    48  	this.exe = exe
    49  
    50  	// 测试环境下不更新
    51  	if Tea.IsTesting() {
    52  		return
    53  	}
    54  
    55  	if this.isInstalling {
    56  		return
    57  	}
    58  	this.isInstalling = true
    59  
    60  	remotelogs.Println("UPGRADE_MANAGER", "upgrading node ...")
    61  	err = this.install()
    62  	if err != nil {
    63  		remotelogs.Error("UPGRADE_MANAGER", "download failed: "+err.Error())
    64  
    65  		this.isInstalling = false
    66  		return
    67  	}
    68  
    69  	remotelogs.Println("UPGRADE_MANAGER", "upgrade successfully")
    70  
    71  	goman.New(func() {
    72  		err = this.restart()
    73  		if err != nil {
    74  			remotelogs.Error("UPGRADE_MANAGER", err.Error())
    75  		}
    76  
    77  		this.isInstalling = false
    78  	})
    79  }
    80  
    81  // IsInstalling 检查是否正在安装
    82  func (this *UpgradeManager) IsInstalling() bool {
    83  	return this.isInstalling
    84  }
    85  
    86  func (this *UpgradeManager) install() error {
    87  	// 检查是否有已下载但未安装成功的
    88  	if len(this.lastFile) > 0 {
    89  		_, err := os.Stat(this.lastFile)
    90  		if err == nil {
    91  			err = this.unzip(this.lastFile)
    92  			if err != nil {
    93  				return err
    94  			}
    95  			this.lastFile = ""
    96  			return nil
    97  		}
    98  	}
    99  
   100  	// 创建临时文件
   101  	var dir = Tea.Root + "/tmp"
   102  	_, err := os.Stat(dir)
   103  	if err != nil {
   104  		if os.IsNotExist(err) {
   105  			err = os.Mkdir(dir, 0777)
   106  			if err != nil {
   107  				return err
   108  			}
   109  		} else {
   110  			return err
   111  		}
   112  	}
   113  
   114  	remotelogs.Println("UPGRADE_MANAGER", "downloading new node ...")
   115  
   116  	var path = dir + "/edge-node.tmp"
   117  	fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	isClosed := false
   122  	defer func() {
   123  		if !isClosed {
   124  			_ = fp.Close()
   125  		}
   126  	}()
   127  
   128  	client, err := rpc.SharedRPC()
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	var offset int64
   134  	var h = md5.New()
   135  	var sum = ""
   136  	var filename = ""
   137  	for {
   138  		resp, err := client.NodeRPC.DownloadNodeInstallationFile(client.Context(), &pb.DownloadNodeInstallationFileRequest{
   139  			Os:          runtime.GOOS,
   140  			Arch:        runtime.GOARCH,
   141  			ChunkOffset: offset,
   142  		})
   143  		if err != nil {
   144  			return err
   145  		}
   146  		if len(resp.Sum) == 0 {
   147  			return nil
   148  		}
   149  		sum = resp.Sum
   150  		filename = resp.Filename
   151  		if stringutil.VersionCompare(resp.Version, teaconst.Version) <= 0 {
   152  			return nil
   153  		}
   154  		if len(resp.ChunkData) == 0 {
   155  			break
   156  		}
   157  
   158  		// 写入文件
   159  		_, err = fp.Write(resp.ChunkData)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		_, err = h.Write(resp.ChunkData)
   164  		if err != nil {
   165  			return err
   166  		}
   167  
   168  		offset = resp.Offset
   169  	}
   170  
   171  	if len(filename) == 0 {
   172  		return nil
   173  	}
   174  
   175  	isClosed = true
   176  	err = fp.Close()
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	if fmt.Sprintf("%x", h.Sum(nil)) != sum {
   182  		_ = os.Remove(path)
   183  		return nil
   184  	}
   185  
   186  	// 改成zip
   187  	zipPath := dir + "/" + filename
   188  	err = os.Rename(path, zipPath)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	this.lastFile = zipPath
   193  
   194  	// 解压
   195  	err = this.unzip(zipPath)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // 解压
   204  func (this *UpgradeManager) unzip(zipPath string) error {
   205  	var isOk = false
   206  	defer func() {
   207  		if isOk {
   208  			// 只有解压并覆盖成功后才会删除
   209  			_ = os.Remove(zipPath)
   210  		}
   211  	}()
   212  
   213  	// 解压
   214  	var target = Tea.Root
   215  	if Tea.IsTesting() {
   216  		// 测试环境下只解压在tmp目录
   217  		target = Tea.Root + "/tmp"
   218  	}
   219  
   220  	// 先改先前的可执行文件
   221  	err := os.Rename(target+"/bin/"+teaconst.ProcessName, target+"/bin/."+teaconst.ProcessName+".dist")
   222  	var hasBackup = err == nil
   223  	defer func() {
   224  		if !isOk && hasBackup {
   225  			// 失败时还原
   226  			_ = os.Rename(target+"/bin/."+teaconst.ProcessName+".dist", target+"/bin/"+teaconst.ProcessName)
   227  		}
   228  	}()
   229  
   230  	var unzip = utils.NewUnzip(zipPath, target, "edge-node/")
   231  	err = unzip.Run()
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	isOk = true
   237  
   238  	return nil
   239  }
   240  
   241  // 重启
   242  func (this *UpgradeManager) restart() error {
   243  	// 关闭当前sock,防止无法重启
   244  	_ = gosock.NewTmpSock(teaconst.ProcessName).Close()
   245  
   246  	// 重新启动
   247  	if DaemonIsOn && DaemonPid == os.Getppid() {
   248  		utils.Exit() // TODO 试着更优雅重启
   249  	} else {
   250  		// quit
   251  		events.Notify(events.EventQuit)
   252  
   253  		// terminated
   254  		events.Notify(events.EventTerminated)
   255  
   256  		// 启动
   257  		var exe = filepath.Dir(this.exe) + "/" + teaconst.ProcessName
   258  		var cmd = executils.NewCmd(exe, "start")
   259  		err := cmd.Start()
   260  		if err != nil {
   261  			return err
   262  		}
   263  
   264  		// 退出当前进程
   265  		time.Sleep(1 * time.Second)
   266  		os.Exit(0)
   267  	}
   268  	return nil
   269  }