[ADD] add windows service manager code

This commit is contained in:
barnett 2018-12-03 18:10:40 +08:00
parent 5641c809b1
commit 77cd220133
12 changed files with 384 additions and 35 deletions

View File

@ -35,8 +35,9 @@ image:
binary:
@echo "🐳build binary ${WHAT} os ${GOOS}"
@ GOOS=${GOOS} bash ./release.sh binary ${WHAT}
run:build image
run-c:image
test/run/run_${WHAT}.sh
run:build
ifeq ($(WHAT),api)
${BIN_PATH}/${BASE_NAME}-api --log-level=debug \
--mysql="root:@tcp(127.0.0.1:3306)/region" \

View File

@ -22,12 +22,16 @@ import (
"fmt"
"os"
"github.com/spf13/pflag"
"github.com/goodrain/rainbond/cmd/node/option"
"github.com/goodrain/rainbond/cmd/node/server"
)
func main() {
server.ParseClientCommnad(os.Args)
option.Config.AddFlags(pflag.CommandLine)
server.InstallServiceFlags(pflag.CommandLine)
option.Init()
if err := server.Run(option.Config); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)

View File

@ -19,6 +19,7 @@
package option
import (
"context"
"flag"
"fmt"
"path"
@ -49,7 +50,6 @@ func Init() error {
if initialized {
return nil
}
Config.AddFlags(pflag.CommandLine)
pflag.Parse()
Config.SetLog()
if err := Config.parse(); err != nil {
@ -154,7 +154,7 @@ func (a *Conf) AddFlags(fs *pflag.FlagSet) {
fs.IntVar(&a.FailTime, "failTime", 3, "the fail time of healthy check")
fs.IntVar(&a.CheckIntervalSec, "checkInterval-second", 5, "the interval time of healthy check")
fs.StringSliceVar(&a.Etcd.Endpoints, "etcd", []string{"http://127.0.0.1:2379"}, "the path of node in etcd")
fs.DurationVar(&a.Etcd.DialTimeout, "etcd-dialTimeOut", 2*time.Second, "etcd cluster dialTimeOut.")
fs.DurationVar(&a.Etcd.DialTimeout, "etcd-dialTimeOut", 3, "etcd cluster dialTimeOut In seconds")
fs.IntVar(&a.ReqTimeout, "reqTimeOut", 2, "req TimeOut.")
fs.Int64Var(&a.TTL, "ttl", 10, "node timeout second")
fs.Int64Var(&a.ProcTTL, "procttl", 600, "proc ttl")
@ -190,16 +190,24 @@ func (a *Conf) SetLog() {
logrus.SetLevel(level)
}
//Parse handle config and create some api
func (a *Conf) Parse() (err error) {
//ParseClient handle config and create some api
func (a *Conf) ParseClient() (err error) {
a.DockerCli, err = dockercli.NewEnvClient()
if err != nil {
return err
}
a.EtcdCli, err = client.New(a.Etcd)
if err != nil {
return err
logrus.Infof("begin create etcd client: %s", a.Etcd.Endpoints)
for {
a.EtcdCli, err = client.New(a.Etcd)
if err != nil {
logrus.Errorf("create etcd client failure %s, will retry after 3 second", err.Error())
}
if err == nil && a.EtcdCli != nil {
break
}
time.Sleep(time.Second * 3)
}
logrus.Infof("create etcd client success")
return nil
}
@ -231,15 +239,19 @@ func cleanKeyPrefix(p string) string {
}
//parse parse
func (c *Conf) parse() error {
if c.Etcd.DialTimeout > 0 {
c.Etcd.DialTimeout *= time.Second
func (a *Conf) parse() error {
if a.Etcd.DialTimeout < 3 {
a.Etcd.DialTimeout = time.Second * 3
} else {
a.Etcd.DialTimeout = a.Etcd.DialTimeout * time.Second
}
if c.TTL <= 0 {
c.TTL = 10
a.Etcd.Context = context.Background()
if a.TTL <= 0 {
a.TTL = 10
}
if c.LockTTL < 2 {
c.LockTTL = 300
if a.LockTTL < 2 {
a.LockTTL = 300
}
return nil
}

View File

@ -33,7 +33,6 @@ import (
// node reg : Register the daemon configuration for node
// node run: daemon start node server
func ParseClientCommnad(args []string) {
fmt.Println(args)
if len(args) > 1 {
switch args[1] {
case "version":

View File

@ -40,11 +40,25 @@ import (
//Run start run
func Run(c *option.Conf) error {
if err := c.Parse(); err != nil {
stop, err := initService(c)
if err != nil {
return err
}
if stop {
return nil
}
nodemanager, err := nodem.NewNodeManager(c)
if err != nil {
return fmt.Errorf("create node manager failed: %s", err)
}
if err := nodemanager.InitStart(); err != nil {
return err
}
if err := c.ParseClient(); err != nil {
return fmt.Errorf("config parse error:%s", err.Error())
}
errChan := make(chan error, 3)
err := eventLog.NewManager(eventLog.EventConfig{
err = eventLog.NewManager(eventLog.EventConfig{
EventLogServers: c.EventLogServer,
DiscoverAddress: c.Etcd.Endpoints,
})
@ -53,24 +67,24 @@ func Run(c *option.Conf) error {
return nil
}
defer eventLog.CloseManager()
logrus.Debug("create and start event log client success")
kubecli, err := kubecache.NewKubeClient(c)
if err != nil {
return err
}
defer kubecli.Stop()
logrus.Debug("create and start kube cache moudle success")
// init etcd client
if err = store.NewClient(c); err != nil {
return fmt.Errorf("Connect to ETCD %s failed: %s", c.Etcd.Endpoints, err)
}
nodemanager, err := nodem.NewNodeManager(c)
if err != nil {
return fmt.Errorf("create node manager failed: %s", err)
}
if err := nodemanager.Start(errChan); err != nil {
return fmt.Errorf("start node manager failed: %s", err)
}
defer nodemanager.Stop()
logrus.Debug("create and start node manager moudle success")
//master服务在node服务之后启动
var ms *masterserver.MasterServer
if c.RunMode == "master" {
@ -85,6 +99,7 @@ func Run(c *option.Conf) error {
return err
}
defer ms.Stop(nil)
logrus.Debug("create and start master server moudle success")
}
//create api manager
apiManager := api.NewManager(*c, nodemanager.GetCurrentNode(), ms, kubecli)
@ -95,6 +110,7 @@ func Run(c *option.Conf) error {
return err
}
defer apiManager.Stop()
logrus.Debug("create and start api server moudle success")
defer controller.Exist(nil)
//step finally: listen Signal

View File

@ -0,0 +1,33 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build !windows
package server
import (
"github.com/goodrain/rainbond/cmd/node/option"
"github.com/spf13/pflag"
)
//InstallServiceFlags install service flag set
func InstallServiceFlags(flags *pflag.FlagSet) {
}
func initService(*option.Conf) (bool, error) {
return false, nil
}

View File

@ -0,0 +1,91 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package server
import (
"errors"
"os"
"os/exec"
"path/filepath"
"github.com/goodrain/rainbond/cmd/node/option"
utilwindows "github.com/goodrain/rainbond/util/windows"
"github.com/spf13/pflag"
"golang.org/x/sys/windows"
)
var (
flRegisterService *bool
flUnregisterService *bool
flServiceName *string
setStdHandle = windows.NewLazySystemDLL("kernel32.dll").NewProc("SetStdHandle")
oldStderr windows.Handle
panicFile *os.File
)
//InstallServiceFlags install service flag set
func InstallServiceFlags(flags *pflag.FlagSet) {
flServiceName = flags.String("service-name", "rainbond-node", "Set the Windows service name")
flRegisterService = flags.Bool("register-service", false, "Register the service and exit")
flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit")
}
func getServicePath() (string, error) {
p, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
return filepath.Abs(p)
}
// initService is the entry point for running the daemon as a Windows
// service. It returns an indication to stop (if registering/un-registering);
// an indication of whether it is running as a service; and an error.
func initService(conf *option.Conf) (bool, error) {
if *flUnregisterService {
if *flRegisterService {
return true, errors.New("--register-service and --unregister-service cannot be used together")
}
return true, unregisterService()
}
if *flRegisterService {
return true, registerService()
}
return false, nil
}
func unregisterService() error {
return nil
}
func registerService() error {
p, err := getServicePath()
if err != nil {
return err
}
// Configure the service to launch with the arguments that were just passed.
args := []string{""}
for _, a := range os.Args[1:] {
if a != "--register-service" && a != "--unregister-service" {
args = append(args, a)
}
}
return utilwindows.RegisterService(*flServiceName, p, "Rainbond NodeManager", []string{}, args)
}

View File

@ -19,8 +19,12 @@
package controller
import (
"fmt"
"strings"
"github.com/goodrain/rainbond/cmd/node/option"
"github.com/goodrain/rainbond/node/nodem/service"
"github.com/goodrain/rainbond/util/windows"
)
//NewController At the stage you want to load the configurations of all rainbond components
@ -37,28 +41,46 @@ type windowsServiceController struct {
}
func (w *windowsServiceController) InitStart(services []*service.Service) error {
for _, s := range services {
if s.IsInitStart {
if err := w.WriteConfig(s); err != nil {
return err
}
if err := w.StartService(s.Name); err != nil {
return fmt.Errorf("start windows service %s failure %s", s.Name, err.Error())
}
}
}
return nil
}
func (w *windowsServiceController) StartService(name string) error {
return nil
return windows.StartService(name)
}
func (w *windowsServiceController) StopService(name string) error {
return nil
return windows.StopService(name)
}
func (w *windowsServiceController) StartList(list []*service.Service) error {
for _, s := range list {
w.StartService(s.Name)
}
return nil
}
func (w *windowsServiceController) StopList(list []*service.Service) error {
for _, s := range list {
w.StopService(s.Name)
}
return nil
}
func (w *windowsServiceController) RestartService(serviceName string) error {
return nil
return windows.RestartService(serviceName)
}
func (w *windowsServiceController) WriteConfig(s *service.Service) error {
return nil
cmds := strings.Split(s.Start, " ")
return windows.RegisterService(s.Name, cmds[0], "Rainbond "+s.Name, s.Requires, cmds)
}
func (w *windowsServiceController) RemoveConfig(name string) error {
return nil
return windows.UnRegisterService(name)
}
func (w *windowsServiceController) EnableService(name string) error {
return nil

View File

@ -191,11 +191,8 @@ func (m *ControllerSystemd) run(args ...string) error {
//InitStart init start. will start some required service
func (m *ControllerSystemd) InitStart(services []*service.Service) error {
if err := m.run("start", "docker"); err != nil {
return fmt.Errorf("systemctl start docker error:%s", err.Error())
}
for _, s := range services {
if s.Name == "etcd" {
if s.IsInitStart {
fileName := fmt.Sprintf("/etc/systemd/system/%s.service", s.Name)
content := ToConfig(s)
if content == "" {

View File

@ -101,14 +101,20 @@ func (n *NodeManager) AddAPIManager(apim *api.Manager) error {
return n.monitor.SetAPIRoute(apim)
}
//Start start
func (n *NodeManager) Start(errchan chan error) error {
//InitStart init start is first start module.
//it would not depend etcd
func (n *NodeManager) InitStart() error {
if err := n.init(); err != nil {
return err
}
if err := n.controller.Start(n.HostNode); err != nil {
return fmt.Errorf("start node controller error,%s", err.Error())
}
return nil
}
//Start start
func (n *NodeManager) Start(errchan chan error) error {
services, err := n.controller.GetAllService()
if err != nil {
return fmt.Errorf("get all services error,%s", err.Error())

View File

@ -38,6 +38,7 @@ type Service struct {
Endpoints []*Endpoint `yaml:"endpoints,omitempty"`
ServiceHealth *Health `yaml:"health"`
OnlyHealthCheck bool `yaml:"only_health_check"`
IsInitStart bool `yaml:"is_init_start"`
Disable bool `yaml:"disable"`
After []string `yaml:"after"`
Requires []string `yaml:"requires"`

View File

@ -0,0 +1,167 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package windows
import (
"fmt"
"time"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
//RegisterService register a service
func RegisterService(serviceName, execPath, displayName string, depends, args []string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
c := mgr.Config{
ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
StartType: mgr.StartAutomatic,
ErrorControl: mgr.ErrorNormal,
Dependencies: depends,
DisplayName: displayName,
}
s, err := m.CreateService(serviceName, execPath, c, args...)
if err != nil {
return err
}
defer s.Close()
// See http://stackoverflow.com/questions/35151052/how-do-i-configure-failure-actions-of-a-windows-service-written-in-go
const (
scActionNone = 0
scActionRestart = 1
scActionReboot = 2
scActionRunCommand = 3
serviceConfigFailureActions = 2
)
type serviceFailureActions struct {
ResetPeriod uint32
RebootMsg *uint16
Command *uint16
ActionsCount uint32
Actions uintptr
}
type scAction struct {
Type uint32
Delay uint32
}
t := []scAction{
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionNone},
}
lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}
err = windows.ChangeServiceConfig2(s.Handle, serviceConfigFailureActions, (*byte)(unsafe.Pointer(&lpInfo)))
if err != nil {
return err
}
return eventlog.Install(serviceName, execPath, false, eventlog.Info|eventlog.Warning|eventlog.Error)
}
//UnRegisterService unres
func UnRegisterService(serviceName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(serviceName)
if err != nil {
return err
}
defer s.Close()
eventlog.Remove(serviceName)
err = s.Delete()
if err != nil {
return err
}
return nil
}
//StartService start a windows service
func StartService(serviceName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(serviceName)
if err != nil {
return fmt.Errorf("could not open service: %v", err)
}
defer s.Close()
err = s.Start()
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}
return nil
}
//StopService stop a windows service
func StopService(serviceName string) error {
return controlService(serviceName, svc.Stop, svc.Stopped)
}
//RestartService restart a windows service
func RestartService(serviceName string) error {
if err := controlService(serviceName, svc.Stop, svc.Stopped); err != nil {
return err
}
return StartService(serviceName)
}
func controlService(name string, c svc.Cmd, to svc.State) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
status, err := s.Control(c)
if err != nil {
return fmt.Errorf("could not send control=%d: %v", c, err)
}
timeout := time.Now().Add(10 * time.Second)
for status.State != to {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", to)
}
time.Sleep(300 * time.Millisecond)
status, err = s.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}