Обновление: компоненты, которых не знает прежний root-установщик (igate-dist), — вторым заходом, сразу или после перезапуска
Старый bj-component-install отклонял обновление целиком из-за незнакомого igate-dist — вместе со своей новой версией (рабочий сервер: установщик .06.2). installerSince: с какой версии установщик знает компонент; отложенное ставится тем же нажатием после нового установщика, а если перезапускался bj-server — само после старта (Update.Pending). Тест TestLKAgentEndToEnd: ждёт счётчик агента (гонка). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -63,7 +63,13 @@ func TestLKAgentEndToEnd(t *testing.T) {
|
|||||||
if resp.StatusCode != http.StatusOK || !out.OK || out.Mode != "agent" || out.BJ["service"] != "bj-server" {
|
if resp.StatusCode != http.StatusOK || !out.OK || out.Mode != "agent" || out.BJ["service"] != "bj-server" {
|
||||||
t.Fatalf("ответ ядра через очередь: код %d, %+v", resp.StatusCode, out)
|
t.Fatalf("ответ ядра через очередь: код %d, %+v", resp.StatusCode, out)
|
||||||
}
|
}
|
||||||
if st := lkAgentStatus(rc.Snapshot()); !st.Connected || st.Served == 0 {
|
// счётчик агент увеличивает после того, как отдал ответ кабинету, — клиент может получить ответ раньше
|
||||||
|
st := lkAgentStatus(rc.Snapshot())
|
||||||
|
for i := 0; i < 40 && (!st.Connected || st.Served == 0); i++ {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
st = lkAgentStatus(rc.Snapshot())
|
||||||
|
}
|
||||||
|
if !st.Connected || st.Served == 0 {
|
||||||
t.Fatalf("состояние канала: %+v", st)
|
t.Fatalf("состояние канала: %+v", st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package lkgateway
|
package lkgateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -184,3 +185,24 @@ func TestCreateUserLoginTaken(t *testing.T) {
|
|||||||
t.Fatal("один администратор — шаг не выполнен")
|
t.Fatal("один администратор — шаг не выполнен")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Старый root-установщик не знает igate-dist (рабочий сервер 06.10.2026: установщик .06.2) — сначала ставится
|
||||||
|
// новый установщик, igate-dist откладывается на второй заход; новый установщик — всё одним заходом.
|
||||||
|
func TestDeferForInstaller(t *testing.T) {
|
||||||
|
st := func(name, inst string, upd, isNew bool) release.ComponentState {
|
||||||
|
return release.ComponentState{Component: release.Component{Name: name}, Installed: inst, Update: upd, New: isNew}
|
||||||
|
}
|
||||||
|
plan := []release.ComponentState{st("bj-component-install", "2026.10.06.2", true, false), st("igate-dist", "", true, true), st("bj-server", "2026.10.06.26", true, false)}
|
||||||
|
kept, deferred := deferForInstaller(plan, plan)
|
||||||
|
if len(kept) != 2 || len(deferred) != 1 || deferred[0] != "igate-dist" {
|
||||||
|
t.Fatalf("старый установщик: оставлено %v, отложено %v", kept, deferred)
|
||||||
|
}
|
||||||
|
plan[0] = st("bj-component-install", "2026.10.06.15", true, false)
|
||||||
|
if kept, deferred := deferForInstaller(plan, plan); len(kept) != 3 || len(deferred) != 0 {
|
||||||
|
t.Fatalf("установщик знает igate-dist: оставлено %d, отложено %v", len(kept), deferred)
|
||||||
|
}
|
||||||
|
plan[0] = st("bj-component-install", "2026.10.06.2", false, false) // установщик не обновляется — откладывать нечем
|
||||||
|
if kept, deferred := deferForInstaller(plan, plan); len(kept) != 3 || len(deferred) != 0 {
|
||||||
|
t.Fatalf("без нового установщика: оставлено %d, отложено %v", len(kept), deferred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ type UpdateSettings struct {
|
|||||||
ComponentsChannel string `json:"components_channel,omitempty"`
|
ComponentsChannel string `json:"components_channel,omitempty"`
|
||||||
// Notified — выпуск, о котором уже написали администратору («Доступно обновление bj»): по разу на выпуск.
|
// Notified — выпуск, о котором уже написали администратору («Доступно обновление bj»): по разу на выпуск.
|
||||||
Notified string `json:"notified,omitempty"`
|
Notified string `json:"notified,omitempty"`
|
||||||
|
// Pending — компоненты, отложенные до нового установщика обновлений (ставятся после перезапуска bj-server).
|
||||||
|
Pending []string `json:"pending,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewsSettings — лента новостей (события системы, окна техработ, обновления
|
// NewsSettings — лента новостей (события системы, окна техработ, обновления
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ const DefaultUpdateURL = "https://lic.zetit.ru/updates"
|
|||||||
// installerKnowsAll — с этой версии root-установщик обновлений знает bj-installer и ставит выделенные модули.
|
// installerKnowsAll — с этой версии root-установщик обновлений знает bj-installer и ставит выделенные модули.
|
||||||
const installerKnowsAll = "2026.09.25"
|
const installerKnowsAll = "2026.09.25"
|
||||||
|
|
||||||
|
// installerSince — с какой версии root-установщик (bj-component-install) знает компонент. Старый установщик
|
||||||
|
// отклоняет обновление целиком, если в нём есть незнакомый компонент — вместе с собственной новой версией
|
||||||
|
// (06.10.2026 на рабочем сервере: установщик .06.2, «неизвестный компонент "igate-dist"»). Такие компоненты
|
||||||
|
// ставятся вторым заходом, уже новым установщиком — сразу или после перезапуска bj-server.
|
||||||
|
var installerSince = map[string]string{"bj-installer": installerKnowsAll, "igate-dist": "2026.10.06.15"}
|
||||||
|
|
||||||
type Updater struct{ rc *RuntimeConfig }
|
type Updater struct{ rc *RuntimeConfig }
|
||||||
|
|
||||||
func NewUpdater(rc *RuntimeConfig) *Updater { return &Updater{rc: rc} }
|
func NewUpdater(rc *RuntimeConfig) *Updater { return &Updater{rc: rc} }
|
||||||
@@ -294,26 +300,9 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
|
|||||||
// из ядра модули: пока он сам не обновлён, такие компоненты откладываются (второй заход). Установщик
|
// из ядра модули: пока он сам не обновлён, такие компоненты откладываются (второй заход). Установщик
|
||||||
// новее этого умеет всё — обновление одним заходом, даже если вместе с ним обновляется и он сам
|
// новее этого умеет всё — обновление одним заходом, даже если вместе с ним обновляется и он сам
|
||||||
// (раньше любое его обновление делило установку на два раза: «скачал 24 МБ — снова „Обновить“ — ещё 6»).
|
// (раньше любое его обновление делило установку на два раза: «скачал 24 МБ — снова „Обновить“ — ещё 6»).
|
||||||
installerOutdated := false
|
selected, deferred := deferForInstaller(plan, selected)
|
||||||
for _, c := range plan {
|
if len(selected) == 0 {
|
||||||
if c.Name == "bj-component-install" && c.Update && (c.Installed == "" || release.IsNewer(installerKnowsAll, c.Installed)) {
|
return "", fmt.Errorf("сначала обновите установщик обновлений (bj-component-install): текущий не ставит %s", strings.Join(deferred, ", "))
|
||||||
installerOutdated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var deferred []string
|
|
||||||
if installerOutdated {
|
|
||||||
kept := selected[:0]
|
|
||||||
for _, c := range selected {
|
|
||||||
if c.New || c.Name == "bj-installer" { // bj-installer прежний установщик обновлений не знает
|
|
||||||
deferred = append(deferred, c.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kept = append(kept, c)
|
|
||||||
}
|
|
||||||
selected = kept
|
|
||||||
if len(selected) == 0 {
|
|
||||||
return "", fmt.Errorf("сначала обновите установщик обновлений (bj-component-install): текущий не ставит модули %s", strings.Join(deferred, ", "))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupStaging(24 * time.Hour)
|
cleanupStaging(24 * time.Hour)
|
||||||
@@ -411,10 +400,25 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
|
|||||||
}
|
}
|
||||||
msg += "; перезапущены службы: " + strings.Join(units, ", ")
|
msg += "; перезапущены службы: " + strings.Join(units, ", ")
|
||||||
}
|
}
|
||||||
if len(deferred) > 0 {
|
|
||||||
msg += "; модули " + strings.Join(deferred, ", ") + " будут установлены после перезапуска bj-server новым установщиком"
|
|
||||||
}
|
|
||||||
msg += unlicensedNote(off)
|
msg += unlicensedNote(off)
|
||||||
|
if len(deferred) > 0 {
|
||||||
|
core := false
|
||||||
|
for _, n := range req.Names {
|
||||||
|
core = core || n == "bj-server"
|
||||||
|
}
|
||||||
|
if core { // bj-server перезапускается — доставит отложенное сам после старта (updateLoop)
|
||||||
|
cfg := u.rc.Snapshot().Update
|
||||||
|
cfg.Pending = deferred
|
||||||
|
_ = u.rc.SaveUpdateSettings(cfg)
|
||||||
|
return msg + "; " + strings.Join(deferred, ", ") + " — сразу после перезапуска bj-server, новым установщиком", nil
|
||||||
|
}
|
||||||
|
log.Printf("lk-gateway: установщик обновлён — второй заход: %s", strings.Join(deferred, ", "))
|
||||||
|
more, err := u.applyUpdate(ctx, deferred)
|
||||||
|
if err != nil {
|
||||||
|
return msg + "; " + strings.Join(deferred, ", ") + " не установлены: " + err.Error(), nil
|
||||||
|
}
|
||||||
|
return msg + "; затем " + more, nil
|
||||||
|
}
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,6 +589,7 @@ func (u *Updater) updateLoop(ctx context.Context) {
|
|||||||
cleanupStaging(time.Hour)
|
cleanupStaging(time.Hour)
|
||||||
enforceModuleLicense(ctx, u.rc)
|
enforceModuleLicense(ctx, u.rc)
|
||||||
_, _ = u.ensureSplitModules(ctx)
|
_, _ = u.ensureSplitModules(ctx)
|
||||||
|
u.applyPending(ctx)
|
||||||
check()
|
check()
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@@ -597,6 +602,50 @@ func (u *Updater) updateLoop(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deferForInstaller — что из выбранного прежний root-установщик не поставит (installerSince, впервые
|
||||||
|
// выделенные модули): откладывается до второго захода, когда в первом обновится сам установщик.
|
||||||
|
func deferForInstaller(plan, selected []release.ComponentState) (kept []release.ComponentState, deferred []string) {
|
||||||
|
installerUpd, installerVer := false, ""
|
||||||
|
for _, c := range plan {
|
||||||
|
if c.Name == "bj-component-install" {
|
||||||
|
installerUpd, installerVer = c.Update, c.Installed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !installerUpd {
|
||||||
|
return selected, nil
|
||||||
|
}
|
||||||
|
older := func(since string) bool { return installerVer == "" || release.IsNewer(since, installerVer) }
|
||||||
|
for _, c := range selected {
|
||||||
|
since, known := installerSince[c.Name]
|
||||||
|
if (known && older(since)) || (c.New && older(installerKnowsAll)) {
|
||||||
|
deferred = append(deferred, c.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kept = append(kept, c)
|
||||||
|
}
|
||||||
|
return kept, deferred
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyPending — компоненты, отложенные прошлым обновлением до нового установщика (обновление шло вместе с
|
||||||
|
// bj-server и перезапустило его): ставятся сами, без второго нажатия «Обновить».
|
||||||
|
func (u *Updater) applyPending(ctx context.Context) {
|
||||||
|
cfg := u.rc.Snapshot().Update
|
||||||
|
if len(cfg.Pending) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
names := cfg.Pending
|
||||||
|
cfg.Pending = nil // один заход: не получилось — человек увидит компонент в списке обновлений
|
||||||
|
_ = u.rc.SaveUpdateSettings(cfg)
|
||||||
|
actx, cancel := context.WithTimeout(ctx, 30*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
msg, err := u.ApplyUpdate(actx, names)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("lk-gateway: отложенные компоненты %s не установлены: %v", strings.Join(names, ", "), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("lk-gateway: отложенные компоненты: %s", msg)
|
||||||
|
}
|
||||||
|
|
||||||
// notifyUpdate — администратору системы: вышел выпуск, который на этом сервере ещё не установлен (по разу на выпуск).
|
// notifyUpdate — администратору системы: вышел выпуск, который на этом сервере ещё не установлен (по разу на выпуск).
|
||||||
func (u *Updater) notifyUpdate(st UpdateStatus) {
|
func (u *Updater) notifyUpdate(st UpdateStatus) {
|
||||||
cfg := u.rc.Snapshot().Update
|
cfg := u.rc.Snapshot().Update
|
||||||
|
|||||||
Reference in New Issue
Block a user