Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: run manager-api as an OS agnostic service #1667

Merged
merged 9 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 132 additions & 65 deletions api/cmd/managerapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
showVersion bool
Version string
GitHash string
service *Service
)

func printInfo() {
Expand All @@ -59,6 +60,14 @@ func printVersion() {

// NewManagerAPICommand creates the manager-api command.
func NewManagerAPICommand() *cobra.Command {
cobra.OnInitialize(func() {
var err error
service, err = createService()
if err != nil {
fmt.Fprintf(os.Stderr, "error occurred while initializing service: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can quit after outputing this message.

Copy link
Member Author

@bisakhmondal bisakhmondal Apr 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I avoided quitting because executing plain ./manager-api doesn't require service struct ref. If somehow createService returns a nil service and service-specific commands are used go runtime manager will throw an error of referencing a nil struct before any line of actual logic of manager-api being executed (slightly ungraceful though). Let me know what you think.

}
})

cmd := &cobra.Command{
Use: "manager-api [flags]",
Short: "APISIX Manager API",
Expand All @@ -68,91 +77,136 @@ func NewManagerAPICommand() *cobra.Command {
printVersion()
os.Exit(0)
}
err := manageAPI()
return err
},
}

conf.InitConf()
log.InitLogger()
cmd.PersistentFlags().StringVarP(&conf.WorkDir, "work-dir", "p", ".", "current work directory")
cmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "show manager-api version")

if err := utils.WritePID(conf.PIDPath); err != nil {
log.Errorf("failed to write pid: %s", err)
panic(err)
}
utils.AppendToClosers(func() error {
if err := os.Remove(conf.PIDPath); err != nil {
log.Errorf("failed to remove pid path: %s", err)
return err
}
return nil
})

droplet.Option.Orchestrator = func(mws []droplet.Middleware) []droplet.Middleware {
var newMws []droplet.Middleware
// default middleware order: resp_reshape, auto_input, traffic_log
// We should put err_transform at second to catch all error
newMws = append(newMws, mws[0], &handler.ErrorTransformMiddleware{}, &filter.AuthenticationMiddleware{})
newMws = append(newMws, mws[1:]...)
return newMws
}
cmd.AddCommand(newStartCommand(), newInstallCommand(), newStatusCommand(), newStopCommand(), newRemoveCommand())
return cmd
}

if err := storage.InitETCDClient(conf.ETCDConfig); err != nil {
log.Errorf("init etcd client fail: %w", err)
panic(err)
}
if err := store.InitStores(); err != nil {
log.Errorf("init stores fail: %w", err)
panic(err)
}
func manageAPI() error {
conf.InitConf()
log.InitLogger()

// routes
r := internal.SetUpRouter()
addr := fmt.Sprintf("%s:%d", conf.ServerHost, conf.ServerPort)
s := &http.Server{
Addr: addr,
Handler: r,
ReadTimeout: time.Duration(1000) * time.Millisecond,
WriteTimeout: time.Duration(5000) * time.Millisecond,
}
if err := utils.WritePID(conf.PIDPath); err != nil {
log.Errorf("failed to write pid: %s", err)
panic(err)
}
utils.AppendToClosers(func() error {
if err := os.Remove(conf.PIDPath); err != nil {
log.Errorf("failed to remove pid path: %s", err)
return err
}
return nil
})

droplet.Option.Orchestrator = func(mws []droplet.Middleware) []droplet.Middleware {
var newMws []droplet.Middleware
// default middleware order: resp_reshape, auto_input, traffic_log
// We should put err_transform at second to catch all error
newMws = append(newMws, mws[0], &handler.ErrorTransformMiddleware{}, &filter.AuthenticationMiddleware{})
newMws = append(newMws, mws[1:]...)
return newMws
}

log.Infof("The Manager API is listening on %s", addr)
if err := storage.InitETCDClient(conf.ETCDConfig); err != nil {
log.Errorf("init etcd client fail: %w", err)
panic(err)
}
if err := store.InitStores(); err != nil {
log.Errorf("init stores fail: %w", err)
panic(err)
}

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// routes
r := internal.SetUpRouter()
addr := fmt.Sprintf("%s:%d", conf.ServerHost, conf.ServerPort)
s := &http.Server{
Addr: addr,
Handler: r,
ReadTimeout: time.Duration(1000) * time.Millisecond,
WriteTimeout: time.Duration(5000) * time.Millisecond,
}

go func() {
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
utils.CloseAll()
log.Fatalf("listen and serv fail: %s", err)
}
}()
log.Infof("The Manager API is listening on %s", addr)

printInfo()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(quit)

sig := <-quit
log.Infof("The Manager API server receive %s and start shutting down", sig.String())
go func() {
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
utils.CloseAll()
log.Fatalf("listen and serv fail: %s", err)
}
}()

ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
printInfo()

if err := s.Shutdown(ctx); err != nil {
log.Errorf("Shutting down server error: %s", err)
}
sig := <-quit
log.Infof("The Manager API server receive %s and start shutting down", sig.String())

log.Infof("The Manager API server exited")
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()

utils.CloseAll()
return nil
},
if err := s.Shutdown(ctx); err != nil {
log.Errorf("Shutting down server error: %s", err)
}

cmd.PersistentFlags().StringVarP(&conf.WorkDir, "work-dir", "p", ".", "current work directory")
cmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "show manager-api version")
log.Infof("The Manager API server exited")

cmd.AddCommand(newStopCommand())
return cmd
utils.CloseAll()
return nil
}

func newStartCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "start Apache APISIX Dashboard service",
RunE: func(cmd *cobra.Command, args []string) error {
ServiceState.startService = true
status, err := service.manageService()
fmt.Printf("%s\n", status)
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
return err
},
}
return cmd
}
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
func newInstallCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "install",
Short: "reinstall Apache APISIX Dashboard service",
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
RunE: func(cmd *cobra.Command, args []string) error {
ServiceState.installService = true
status, err := service.manageService()
fmt.Printf("%s\n", status)
return err
},
}
return cmd
}
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
func newStatusCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "status of Apache APISIX Dashboard service",
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
RunE: func(cmd *cobra.Command, args []string) error {
ServiceState.status = true
status, err := service.manageService()
fmt.Printf("%s\n", status)
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
return err
},
}
return cmd
}
func newStopCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "stop",
Use: "stop",
Short: "stop Apache APISIX Dashboard service/program",
Run: func(cmd *cobra.Command, args []string) {
pid, err := utils.ReadPID(conf.PIDPath)
if err != nil {
Expand All @@ -170,3 +224,16 @@ func newStopCommand() *cobra.Command {
}
return cmd
}
func newRemoveCommand() *cobra.Command {
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
cmd := &cobra.Command{
Use: "remove",
Short: "remove Apache APISIX Dashboard service",
RunE: func(cmd *cobra.Command, args []string) error {
ServiceState.removeService = true
status, err := service.manageService()
fmt.Printf("%s\n", status)
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
return err
},
}
return cmd
}
93 changes: 93 additions & 0 deletions api/cmd/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 cmd

import (
"os"
"runtime"

"github.com/apisix/manager-api/internal/conf"
"github.com/takama/daemon"
)

type Service struct {
daemon.Daemon
}

var ServiceState struct {
bisakhmondal marked this conversation as resolved.
Show resolved Hide resolved
startService bool
stopService bool
installService bool
removeService bool
status bool
}

func createService() (*Service, error) {
var d daemon.Daemon
var err error
if runtime.GOOS == "darwin" {
d, err = daemon.New("apisix-dashboard", "Apache APISIX Dashboard", daemon.GlobalDaemon)
} else {
d, err = daemon.New("apisix-dashboard", "Apache APISIX Dashboard", daemon.SystemDaemon)
}
if err != nil {
return nil, err
}
service := &Service{d}
return service, nil
}

func (service *Service) manageService() (string, error) {
if ServiceState.status {
return service.Status()
}
if ServiceState.removeService {
return service.Remove()
}
if conf.WorkDir == "." {
dir, err := os.Getwd()
if err != nil {
return "proceed with --work-dir flag", err
}
conf.WorkDir = dir
}
if ServiceState.installService {
return service.Install("-p", conf.WorkDir)
}
if ServiceState.startService {
iStatus, err := service.Install("-p", conf.WorkDir)
if err != nil {
if err != daemon.ErrAlreadyInstalled {
return iStatus, err
}
iStatus = ""
}
sStatus, err := service.Start()
if iStatus != "" {
sStatus = iStatus + "\n" + sStatus
}
return sStatus, err
} else if ServiceState.stopService {
return service.Stop()
}

err := manageAPI()
if err != nil {
return "Unable to start Manager API", err
}
return "The Manager API server exited", nil
}
1 change: 1 addition & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/sony/sonyflake v1.0.0
github.com/spf13/cobra v0.0.3
github.com/stretchr/testify v1.6.1
github.com/takama/daemon v1.0.0 // indirect
github.com/tidwall/gjson v1.6.7
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 // indirect
github.com/xeipuuv/gojsonschema v1.2.0
Expand Down
4 changes: 4 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULU
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down Expand Up @@ -376,6 +377,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ=
github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE=
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
Expand Down Expand Up @@ -512,6 +515,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
Expand Down
Loading