| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package lbagent
- import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "strings"
- "sync"
- "syscall"
- "time"
- "unicode"
- "github.com/fsnotify/fsnotify"
- "yunion.io/x/log"
- api "yunion.io/x/onecloud/pkg/apis/compute"
- agentutils "yunion.io/x/onecloud/pkg/lbagent/utils"
- )
- const (
- HA_STATE_SCRIPT_NAME = "ha_state.sh"
- HA_STATE_SCRIPT_CONTENT = `
- #!/bin/bash
- echo "keepalived notify $@" > /proc/1/fd/1
- echo "$@" >%s
- `
- HA_STATE_FILENAME = "ha_state"
- )
- type HaStateProvider interface {
- StateChannel() <-chan string
- StateScript() string
- }
- type HaStateWatcher struct {
- HaStateScriptPath string
- HaStatePath string
- CurrentState string // TODO hide it
- opts *Options
- w *fsnotify.Watcher
- C chan string
- }
- func (hsw *HaStateWatcher) StateChannel() <-chan string {
- return hsw.C
- }
- func (hsw *HaStateWatcher) StateScript() string {
- return hsw.HaStateScriptPath
- }
- func (hsw *HaStateWatcher) Run(ctx context.Context) {
- defer func() {
- log.Infof("ha state watcher bye")
- wg := ctx.Value("wg").(*sync.WaitGroup)
- wg.Done()
- }()
- hsw.loadHaState()
- hsw.C <- hsw.CurrentState
- statePending := false
- tick := time.NewTicker(3 * time.Second)
- defer tick.Stop()
- for {
- select {
- case ev := <-hsw.w.Events:
- switch {
- case ev.Name == hsw.opts.haproxyRunDir:
- switch ev.Op {
- case fsnotify.Remove, fsnotify.Rename:
- log.Errorf("run dir %s", ev.Op.String())
- hsw.sayBye()
- default:
- log.Debugf("ignored %s", ev)
- }
- case ev.Name == hsw.HaStatePath:
- log.Infof("hastate file: %s", ev)
- switch ev.Op {
- case fsnotify.Create, fsnotify.Write:
- err := hsw.loadHaState()
- if err != nil {
- log.Errorf("load state: %s", err)
- hsw.sayBye()
- }
- select {
- case hsw.C <- hsw.CurrentState:
- statePending = false
- default:
- statePending = true
- }
- }
- }
- case err := <-hsw.w.Errors:
- log.Errorf("watcher err: %s", err)
- hsw.sayBye()
- case <-tick.C:
- if statePending {
- select {
- case hsw.C <- hsw.CurrentState:
- statePending = false
- default:
- }
- }
- case <-ctx.Done():
- return
- }
- }
- }
- func (hsw *HaStateWatcher) loadHaState() (err error) {
- defer func() {
- if err != nil {
- hsw.CurrentState = api.LB_HA_STATE_UNKNOWN
- }
- }()
- data, err := ioutil.ReadFile(hsw.HaStatePath)
- if err != nil {
- return err
- }
- log.Infof("got state: %s", data)
- // Sample content
- //
- // INSTANCE YunionLB BACKUP 110
- //
- fields := strings.Fields(string(data))
- if len(fields) >= 3 {
- hsw.CurrentState = fields[2]
- return
- }
- err = fmt.Errorf("ha state file contains too little info")
- return
- }
- func (hsw *HaStateWatcher) sayBye() {
- syscall.Kill(os.Getpid(), syscall.SIGTERM)
- }
- func NewHaStateWatcher(opts *Options) (hsw *HaStateWatcher, err error) {
- var (
- w *fsnotify.Watcher
- )
- defer func() {
- if err != nil {
- if w != nil {
- w.Close()
- }
- }
- }()
- haStateScriptPath := path.Join(opts.haproxyShareDir, HA_STATE_SCRIPT_NAME)
- haStatePath := path.Join(opts.haproxyRunDir, HA_STATE_FILENAME)
- {
- content := fmt.Sprintf(HA_STATE_SCRIPT_CONTENT, haStatePath)
- content = strings.TrimLeftFunc(content, unicode.IsSpace)
- mode := agentutils.FileModeFileExec
- err = ioutil.WriteFile(haStateScriptPath, []byte(content), mode)
- if err != nil {
- return
- }
- var fi os.FileInfo
- fi, err = os.Stat(haStateScriptPath)
- if err != nil {
- return
- }
- if fi.Mode() != mode {
- err = os.Chmod(haStateScriptPath, mode)
- if err != nil {
- return
- }
- }
- }
- w, err = fsnotify.NewWatcher()
- if err != nil {
- return
- }
- err = w.Add(opts.haproxyRunDir)
- if err != nil {
- return
- }
- hsw = &HaStateWatcher{
- HaStateScriptPath: haStateScriptPath,
- HaStatePath: haStatePath,
- CurrentState: api.LB_HA_STATE_UNKNOWN,
- opts: opts,
- w: w,
- C: make(chan string),
- }
- return
- }
|