You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2021/11/02 08:03:11 UTC

[apisix-dashboard] branch master updated: refactor: remove built-in daemon manager (#2183)

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 3218379  refactor: remove built-in daemon manager (#2183)
3218379 is described below

commit 32183799dd7943a1b9bb3ae38fa2b682979b73b9
Author: bzp2010 <bz...@apache.org>
AuthorDate: Tue Nov 2 03:02:27 2021 -0500

    refactor: remove built-in daemon manager (#2183)
---
 .github/actions/tmate-action           |   1 +
 .github/workflows/backend-cli-test.yml |  13 +-
 .gitmodules                            |   3 +
 api/cmd/install.go                     |  38 --
 api/cmd/remove.go                      |  36 --
 api/cmd/root.go                        |  28 +-
 api/cmd/service.go                     |  94 ----
 api/cmd/start.go                       |  36 --
 api/cmd/status.go                      |  36 --
 api/cmd/stop.go                        |  54 ---
 api/internal/conf/conf.go              |   1 -
 api/internal/core/server/server.go     |  27 +-
 api/service/apisix-dashboard.service   |   8 +
 api/test/shell/cli_test.sh             | 781 ++++++++++++++-------------------
 docs/en/latest/config.json             |   2 +-
 docs/en/latest/deploy-with-docker.md   |   2 +-
 docs/en/latest/deploy-with-rpm.md      |  45 --
 docs/en/latest/deploy.md               | 133 ------
 docs/en/latest/install.md              | 170 +++++++
 19 files changed, 521 insertions(+), 987 deletions(-)

diff --git a/.github/actions/tmate-action b/.github/actions/tmate-action
new file mode 160000
index 0000000..19a49ed
--- /dev/null
+++ b/.github/actions/tmate-action
@@ -0,0 +1 @@
+Subproject commit 19a49ed72830f0810df81e42252ee9da19c8e4d8
diff --git a/.github/workflows/backend-cli-test.yml b/.github/workflows/backend-cli-test.yml
index 23234d7..430a935 100644
--- a/.github/workflows/backend-cli-test.yml
+++ b/.github/workflows/backend-cli-test.yml
@@ -29,6 +29,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v2
+        with:
+          submodules: recursive
 
       - name: cache etcd
         id: cache-etcd
@@ -45,6 +47,15 @@ jobs:
           wget https://github.com/etcd-io/etcd/releases/download/v${{ matrix.etcd }}/etcd-v${{ matrix.etcd }}-linux-amd64.tar.gz
           tar zxvf etcd-v${{ matrix.etcd }}-linux-amd64.tar.gz
 
+      - name: install bats
+        run: |
+          git clone https://github.com/sstephenson/bats.git && cd bats
+          sudo ./install.sh /usr/local
+
       - name: run test
         working-directory: ./api
-        run: sudo ./test/shell/cli_test.sh
+        run: chmod +x ./test/shell/cli_test.sh && sudo ./test/shell/cli_test.sh
+
+      - name: tmate debugger
+        if: ${{ failure() }}
+        uses: ./.github/actions/tmate-action
diff --git a/.gitmodules b/.gitmodules
index c8b78ea..78bfe0d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
 [submodule ".github/actions/paths-filter"]
 	path = .github/actions/paths-filter
 	url = https://github.com/dorny/paths-filter.git
+[submodule ".github/actions/tmate-action"]
+	path = .github/actions/tmate-action
+	url = https://github.com/mxschmitt/action-tmate
diff --git a/api/cmd/install.go b/api/cmd/install.go
deleted file mode 100644
index 03e78b1..0000000
--- a/api/cmd/install.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 (
-	"fmt"
-
-	"github.com/spf13/cobra"
-)
-
-var service *Service
-
-func newInstallCommand() *cobra.Command {
-	return &cobra.Command{
-		Use:   "install",
-		Short: "re-install Apache APISIX Dashboard service",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			serviceState.installService = true
-			status, err := service.manageService()
-			fmt.Println(status)
-			return err
-		},
-	}
-}
diff --git a/api/cmd/remove.go b/api/cmd/remove.go
deleted file mode 100644
index f487a9a..0000000
--- a/api/cmd/remove.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 (
-	"fmt"
-
-	"github.com/spf13/cobra"
-)
-
-func newRemoveCommand() *cobra.Command {
-	return &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.Println(status)
-			return err
-		},
-	}
-}
diff --git a/api/cmd/root.go b/api/cmd/root.go
index 6f830e3..984f54e 100644
--- a/api/cmd/root.go
+++ b/api/cmd/root.go
@@ -29,12 +29,8 @@ import (
 	"github.com/apisix/manager-api/internal/log"
 )
 
-var (
-	forceStart bool
-)
-
 var rootCmd = &cobra.Command{
-	Use:   "manager-api [flags]",
+	Use:   "manager-api",
 	Short: "Apache APISIX Manager API",
 	RunE: func(cmd *cobra.Command, args []string) error {
 		err := manageAPI()
@@ -43,31 +39,17 @@ var rootCmd = &cobra.Command{
 }
 
 func init() {
-	cobra.OnInitialize(func() {
-		var err error
-		service, err = createService()
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "error occurred while initializing service: %s", err)
-		}
-	})
-
 	rootCmd.PersistentFlags().StringVarP(&conf.ConfigFile, "config", "c", "", "config file")
 	rootCmd.PersistentFlags().StringVarP(&conf.WorkDir, "work-dir", "p", ".", "current work directory")
-	rootCmd.PersistentFlags().BoolVarP(&forceStart, "force", "f", false, "force start manager-api")
 
 	rootCmd.AddCommand(
 		newVersionCommand(),
-		newInstallCommand(),
-		newRemoveCommand(),
-		newStartCommand(),
-		newStopCommand(),
-		newStatusCommand(),
 	)
 }
 
 func Execute() {
 	if err := rootCmd.Execute(); err != nil {
-		_, _ = fmt.Fprintln(os.Stderr, err.Error())
+		_, _ = fmt.Fprintln(os.Stderr, err)
 	}
 }
 
@@ -75,9 +57,7 @@ func manageAPI() error {
 	conf.InitConf()
 	log.InitLogger()
 
-	s, err := server.NewServer(&server.Options{
-		ForceStart: forceStart,
-	})
+	s, err := server.NewServer(&server.Options{})
 	if err != nil {
 		return err
 	}
@@ -94,7 +74,7 @@ func manageAPI() error {
 	case sig := <-quit:
 		log.Infof("The Manager API server receive %s and start shutting down", sig.String())
 		s.Stop()
-		log.Infof("The Manager API server exited")
+		log.Infof("See you next time!")
 	case err := <-errSig:
 		log.Errorf("The Manager API server start failed: %s", err.Error())
 		return err
diff --git a/api/cmd/service.go b/api/cmd/service.go
deleted file mode 100644
index af187e3..0000000
--- a/api/cmd/service.go
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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/takama/daemon"
-
-	"github.com/apisix/manager-api/internal/conf"
-)
-
-type Service struct {
-	daemon.Daemon
-}
-
-var serviceState struct {
-	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
-}
diff --git a/api/cmd/start.go b/api/cmd/start.go
deleted file mode 100644
index 6818b17..0000000
--- a/api/cmd/start.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 (
-	"fmt"
-
-	"github.com/spf13/cobra"
-)
-
-func newStartCommand() *cobra.Command {
-	return &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.Println(status)
-			return err
-		},
-	}
-}
diff --git a/api/cmd/status.go b/api/cmd/status.go
deleted file mode 100644
index ca78d42..0000000
--- a/api/cmd/status.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 (
-	"fmt"
-
-	"github.com/spf13/cobra"
-)
-
-func newStatusCommand() *cobra.Command {
-	return &cobra.Command{
-		Use:   "status",
-		Short: "inspect the status of Apache APISIX Dashboard service",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			serviceState.status = true
-			status, err := service.manageService()
-			fmt.Println(status)
-			return err
-		},
-	}
-}
diff --git a/api/cmd/stop.go b/api/cmd/stop.go
deleted file mode 100644
index 02d147e..0000000
--- a/api/cmd/stop.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 (
-	"fmt"
-	"os"
-	"syscall"
-
-	"github.com/spf13/cobra"
-
-	"github.com/apisix/manager-api/internal/conf"
-	"github.com/apisix/manager-api/internal/utils"
-)
-
-func newStopCommand() *cobra.Command {
-	return &cobra.Command{
-		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 {
-				if syscall.ENOENT.Error() != err.Error() {
-					fmt.Fprintf(os.Stderr, "failed to get manager-api pid: %s\n", err)
-				} else {
-					fmt.Fprintf(os.Stderr, "pid path %s not found, is manager-api running?\n", conf.PIDPath)
-				}
-				return
-			}
-			p, err := os.FindProcess(pid)
-			if err != nil {
-				fmt.Fprintf(os.Stderr, "failed to find process manager-api: %s\n", err)
-				return
-			}
-			if err := p.Signal(syscall.SIGINT); err != nil {
-				fmt.Fprintf(os.Stderr, "failed to kill manager-api: %s", err)
-			}
-		},
-	}
-}
diff --git a/api/internal/conf/conf.go b/api/internal/conf/conf.go
index ccbfec1..de599c0 100644
--- a/api/internal/conf/conf.go
+++ b/api/internal/conf/conf.go
@@ -59,7 +59,6 @@ var (
 	AuthConf         Authentication
 	SSLDefaultStatus = 1 //enable ssl by default
 	ImportSizeLimit  = 10 * 1024 * 1024
-	PIDPath          = "/tmp/manager-api.pid"
 	AllowList        []string
 	Plugins          = map[string]bool{}
 )
diff --git a/api/internal/core/server/server.go b/api/internal/core/server/server.go
index 07c4b1c..c46ab3a 100644
--- a/api/internal/core/server/server.go
+++ b/api/internal/core/server/server.go
@@ -35,9 +35,7 @@ type server struct {
 	options   *Options
 }
 
-type Options struct {
-	ForceStart bool // force start new instance
-}
+type Options struct {}
 
 // NewServer Create a server manager
 func NewServer(options *Options) (*server, error) {
@@ -52,13 +50,6 @@ func (s *server) Start(errSig chan error) {
 		return
 	}
 
-	// write daemon pid file
-	err = s.writePID()
-	if err != nil {
-		errSig <- err
-		return
-	}
-
 	// print server info to stdout
 	s.printInfo()
 
@@ -104,22 +95,6 @@ func (s *server) init() error {
 	return nil
 }
 
-func (s *server) writePID() error {
-	if err := utils.WritePID(conf.PIDPath, s.options.ForceStart); err != nil {
-		log.Errorf("failed to write pid: %s", err)
-		return 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
-	})
-
-	return nil
-}
-
 func (s *server) shutdownServer(server *http.Server) {
 	if server != nil {
 		ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
diff --git a/api/service/apisix-dashboard.service b/api/service/apisix-dashboard.service
new file mode 100644
index 0000000..e56f75d
--- /dev/null
+++ b/api/service/apisix-dashboard.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=apisix-dashboard
+Conflicts=apisix-dashboard.service
+After=network-online.target
+
+[Service]
+WorkingDirectory=/usr/local/apisix-dashboard
+ExecStart=/usr/local/apisix-dashboard/manager-api -c /usr/local/apisix-dashboard/conf/conf.yaml
diff --git a/api/test/shell/cli_test.sh b/api/test/shell/cli_test.sh
old mode 100755
new mode 100644
index 8dda240..ef903ec
--- a/api/test/shell/cli_test.sh
+++ b/api/test/shell/cli_test.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env bats
 
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
@@ -17,563 +17,422 @@
 # limitations under the License.
 #
 
-set -ex
+# !!!NOTICE!!!
+# Try to run cli tests (such as virtual machines) in an isolated environment.
+# This test will register the systemd service and run the test. Although
+# environmental cleaning will eventually be running, it may still pollute
+# your environment.
+#
+# For developers who have not modified the CLI part of the program, you do
+# not need to run this test locally, but rely on the test results of GitHub
+# workflow. If you have any improvement suggestions for this test case, you
+# are welcome to submit a PR for modification.
+
 VERSION=$(cat ./VERSION)
 KERNEL=$(uname -s)
+CONF_FILE="/usr/local/apisix-dashboard/conf/conf.yaml"
+LOG_FILE="/usr/local/apisix-dashboard/logs/error.log"
+ACCESS_LOG_FILE="/usr/local/apisix-dashboard/logs/access.log"
+SERVICE_NAME="apisix-dashboard"
 
-# test content in .githash
 if [[ -f ../.githash ]]; then
-    GITHASH=$(cat ../.githash)
-    if [[ ! $GITHASH =~ ^[a-z0-9]{7}$ ]]; then
-        echo "failed: verify .githash content failed"
-        exit 1
-    fi
+  GITHASH=$(cat ../.githash)
+  if [[ ! $GITHASH =~ ^[a-z0-9]{7}$ ]]; then
+    echo "failed: verify .githash content failed"
+    exit 1
+  fi
 else
-    GITHASH=$(HASH="ref: HEAD"; while [[ $HASH == ref\:* ]]; do HASH="$(cat "../.git/$(echo $HASH | cut -d \  -f 2)")"; done; echo ${HASH:0:7})
+  GITHASH=$(HASH="ref: HEAD"; while [[ $HASH == ref\:* ]]; do HASH="$(cat "../.git/$(echo $HASH | cut -d \  -f 2)")"; done; echo ${HASH:0:7})
 fi
 
-clean_up() {
-    git checkout conf/conf.yaml
+recover_conf() {
+  run cp -rf ./conf/conf.yaml ${CONF_FILE}
+  [ "$status" -eq 0 ]
+}
+check_logfile() {
+  [ -f $LOG_FILE ]
+}
+clean_logfile() {
+  echo > $LOG_FILE
 }
 
-logfile="./logs/error.log"
+start_dashboard() {
+  run systemctl start ${SERVICE_NAME}
+  [ "$status" -eq 0 ]
+  sleep $1
+}
 
-check_logfile() {
-    if [[ ! -f $logfile ]]; then
-        echo "failed: failed to write log"
-        exit 1
-    fi
+stop_dashboard() {
+  run systemctl stop ${SERVICE_NAME}
+  [ "$status" -eq 0 ]
+  sleep $1
 }
 
-clean_logfile() {
-    echo > $logfile
+### Test Case
+#pre
+@test "Build and Deploy APISIX Dashboard Manager API" {
+  run go build -o ./manager-api -ldflags "-X github.com/apisix/manager-api/internal/utils.version=${VERSION} -X github.com/apisix/manager-api/internal/utils.gitHash=${GITHASH}" ./main.go
+  [ "$status" -eq 0 ]
+
+  # prepare service files
+  mkdir -p /usr/local/apisix-dashboard/conf /usr/local/apisix-dashboard/logs
+  cp ./conf/* /usr/local/apisix-dashboard/conf
+  cp ./manager-api /usr/local/apisix-dashboard
+
+  # create systemd service
+  cp ./service/apisix-dashboard.service /usr/lib/systemd/system/${SERVICE_NAME}.service
+  run systemctl daemon-reload
+  [ "$status" -eq 0 ]
 }
 
-trap clean_up EXIT
+#1
+@test "Check warn log level" {
+  start_dashboard 3
 
-export GO111MODULE=on
-go build -o ./manager-api -ldflags "-X github.com/apisix/manager-api/internal/utils.version=${VERSION} -X github.com/apisix/manager-api/internal/utils.gitHash=${GITHASH}" ./main.go
+  stop_dashboard 6
 
-# default level: warn, path: logs/error.log
+  check_logfile
 
-./manager-api &
-sleep 3
-./manager-api stop
-sleep 6
+  [ $(grep -c "INFO" "${LOG_FILE}") -eq '0' ]
 
-check_logfile
+  clean_logfile
+}
 
-if [[ `grep -c "INFO" ${logfile}` -ne '0' ]]; then
-    echo "failed: should not write info log when level is warn"
-    exit 1
-fi
+#2
+@test "Check info log leve and signal" {
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" 's/level: warn/level: info/' ${CONF_FILE}
+  else
+    sed -i 's/level: warn/level: info/' ${CONF_FILE}
+  fi
 
-clean_logfile
+  start_dashboard 3
 
-# change level and test signal
+  stop_dashboard 6
 
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" 's/level: warn/level: info/' conf/conf.yaml
-else
-  sed -i 's/level: warn/level: info/' conf/conf.yaml
-fi
+  check_logfile
 
-./manager-api &>/dev/null &
-sleep 3
-./manager-api stop
-sleep 6
+  [ $(grep -c "server receive terminated" "${LOG_FILE}") -eq '1' ]
 
-check_logfile
+  clean_logfile
+}
 
-if [[ `ps -ef | grep "[m]anager-api" -c` -eq '1' ]]; then
-    echo "failed: the manager server didn't deal with signal in correct way"
-    exit 1
-fi
+#3
+@test "Check start info" {
+  LOGLEVEL=$(cat "$CONF_FILE" | awk '$1=="level:"{print $2}')
+  HOST=$(cat "$CONF_FILE" | awk '$1=="host:"{print $2}')
+  PORT=$(cat "$CONF_FILE" | awk '$1=="port:"{print $2}')
+  start_dashboard 3
 
-if [[ `grep -c "server receive interrupt" ${logfile}` -ne '1' ]]; then
-    echo "failed: the manager server didn't deal with signal in correct way"
-    exit 1
-fi
+  run systemctl status ${SERVICE_NAME}
 
-clean_logfile
+  [ $(echo "$output" | grep -c "The manager-api is running successfully\!") -eq '1' ]
+  [ $(echo "$output" | grep -c -w "${VERSION}") -eq '1' ]
+  [ $(echo "$output" | grep -c "${GITHASH}") -eq '1' ]
+  [ $(echo "$output" | grep -c "${LOGLEVEL}") -eq '1' ]
+  [ $(echo "$output" | grep -c "${HOST}:${PORT}") -eq '1' ]
 
-# change path
+  stop_dashboard 6
+}
 
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" 's/logs\/error.log/.\/error.log/' conf/conf.yaml
-else
-  sed -i 's/logs\/error.log/.\/error.log/' conf/conf.yaml
-fi
+#4
+@test "Check version sub-command" {
+  run /usr/local/apisix-dashboard/manager-api version
 
-./manager-api &
-sleep 3
-./manager-api stop
-sleep 6
+  [ $(echo "$output" | grep -c "$VERSION") -eq '1' ]
+  [ $(echo "$output" | grep -c "$GITHASH") -eq '1' ]
+}
 
-check_logfile
+#5
+@test "Check static file server" {
+  # create html directory
+  mkdir -p /usr/local/apisix-dashboard/html
+  echo "hi~" >> /usr/local/apisix-dashboard/html/index.html
 
-if [[ `grep -c "INFO" ./error.log` -eq '0' ]]; then
-    echo "failed: failed to write log on right level"
-    exit 1
-fi
+  # start Manager API
+  start_dashboard 3
 
-# run on a different path
-workDir=$(pwd)
-rm -rf html
-mkdir html
-cd html
-echo "hi~" >> index.html
-APISIX_API_WORKDIR=$workDir $workDir/manager-api &
-sleep 5
-
-res=$(curl http://127.0.0.1:9000)
-$workDir/manager-api stop
-sleep 6
-cd -
-rm -rf html
-
-if [[ $res != "hi~" ]]; then
-    echo "failed: manager-api can't run on a different path"
-    exit 1
-fi
-clean_up
-
-# run with -p flag out of the default directory
-workDir=$(pwd)
-distDir=/tmp/manager-api
-cp -r $workDir $distDir
-cd $distDir && rm -rf bin && mkdir bin && mv ./manager-api ./bin/
-cd $distDir && rm -rf html && mkdir html && echo "hi~" >> html/index.html
-cd $distDir/bin && ./manager-api -p $distDir &
-sleep 5
-
-res=$(curl http://127.0.0.1:9000)
-$distDir/bin/manager-api stop
-sleep 6
-rm -fr $distDir
-
-if [[ $res != "hi~" ]]; then
-    echo "failed: manager-api can't run with -p flag out of the default directory"
-    exit 1
-fi
-cd $workDir && git checkout conf/conf.yaml
+  # request index page
+  result=$(curl "http://127.0.0.1:9000")
+  [ "$result" = "hi~" ]
 
-# test start info
+  stop_dashboard 6
 
-LOGLEVEL=$(cat conf/conf.yaml | awk '$1=="level:"{print $2}')
-HOST=$(cat conf/conf.yaml | awk '$1=="host:"{print $2}')
-PORT=$(cat conf/conf.yaml | awk '$1=="port:"{print $2}')
-STDOUT=/tmp/manager-api
-./manager-api &> ${STDOUT} &
-sleep 3
-./manager-api stop
-sleep 6
+  recover_conf
+}
 
-if [[ `grep -c "The manager-api is running successfully\!" ${STDOUT}` -ne '1' ]]; then
-    echo "failed: the manager server didn't show started info"
-    exit 1
-fi
+#6
+@test "Check invalid etcd endpoint" {
+  recover_conf
 
-if [[ `grep -c -w "${VERSION}" ${STDOUT}` -ne '1' ]]; then
-    echo "failed: the manager server didn't show started info"
-    exit 1
-fi
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" 's/127.0.0.1:2379/127.0.0.0:2379/' ${CONF_FILE}
+  else
+    sed -i 's/127.0.0.1:2379/127.0.0.0:2379/' ${CONF_FILE}
+  fi
 
-if [[ `grep -c "${GITHASH}" ${STDOUT}` -ne '1' ]]; then
-    echo "failed: the manager server didn't show started info"
-    exit 1
-fi
+  start_dashboard 6
 
-if [[ `grep -c "${LOGLEVEL}" ${STDOUT}` -ne '1' ]]; then
-    echo "failed: the manager server didn't show started info"
-    exit 1
-fi
+  run journalctl -u ${SERVICE_NAME}.service -n 30
 
-if [[ `grep -c "0.0.0.0:${PORT}" ${STDOUT}` -ne '1' ]]; then
-    echo "failed: the manager server didn't show started info"
-    exit 1
-fi
+  [ $(echo "$output" | grep -c "Error while dialing dial tcp") -eq '1' ]
 
-# test version command
-out=$(./manager-api version 2>&1 || true)
-if [[ `echo $out | grep -c $VERSION` -ne '1' ]]; then
-    echo "failed: the manager server didn't show version info"
-    exit 1
-fi
+  stop_dashboard 6
+}
 
-if [[ `echo $out | grep -c $GITHASH` -ne '1' ]]; then
-    echo "failed: the manager server didn't show git hash info"
-    exit 1
-fi
+#7
+@test "Check assess log" {
+  recover_conf
 
+  start_dashboard 3
 
-# set an invalid etcd endpoint
+  run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  [ "$status" -eq 0 ]
 
-clean_up
+  stop_dashboard 6
 
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" 's/127.0.0.1:2379/127.0.0.0:2379/' conf/conf.yaml
-else
-  sed -i 's/127.0.0.1:2379/127.0.0.0:2379/' conf/conf.yaml
-fi
+  [ $(grep -c "/apisix/admin/user/login" "${ACCESS_LOG_FILE}") -ne '0' ]
+}
 
+#8
+@test "Check ip allow list" {
+  recover_conf
 
-./manager-api > output.log 2>&1 &
-sleep 6
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" 's@- 127.0.0.1 @- 10.0.0.1 @' ${CONF_FILE}
+  else
+    sed -i 's@- 127.0.0.1 @- 10.0.0.1 @' ${CONF_FILE}
+  fi
 
-cat ${logfile}
+  start_dashboard 3
 
-if [[ `grep -c "server/store.go" ${logfile}` -ne '1' ]]; then
-    echo "failed: failed to write the correct caller"
-    exit 1
-fi
+  run curl -k -i -m 20 -o /dev/null -s -w %{http_code} "http://127.0.0.1:9000"
+  [ "$output" -eq 403 ]
 
-./manager-api stop
-sleep 6
-# clean config
-clean_up
+  stop_dashboard 6
+}
 
-# access log test
-./manager-api &
-sleep 3
+#9
+@test "Check HTTPS server" {
+  recover_conf
 
-curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" 's@# ssl:@ssl:@' ${CONF_FILE}
+    sed -i "" 's@#   port: 9001@  port: 9001@' ${CONF_FILE}
+    sed -i "" "s@#   cert: \"/tmp/cert/example.crt\"@  cert: \"$(pwd)/test/certs/test2.crt\"@" ${CONF_FILE}
+    sed -i "" "s@#   key:  \"/tmp/cert/example.key\"@  cert: \"$(pwd)/test/certs/test2.key\"@" ${CONF_FILE}
+  else
+    sed -i 's@# ssl:@ssl:@' ${CONF_FILE}
+    sed -i 's@#   port: 9001@  port: 9001@' ${CONF_FILE}
+    sed -i "s@#   cert: \"/tmp/cert/example.crt\"@  cert: \"$(pwd)/test/certs/test2.crt\"@" ${CONF_FILE}
+    sed -i "s@#   key:  \"/tmp/cert/example.key\"@  key: \"$(pwd)/test/certs/test2.key\"@" ${CONF_FILE}
+  fi
 
-./manager-api stop
-sleep 6
+  start_dashboard 3
 
-if [[ `grep -c "/apisix/admin/user/login" ./logs/access.log` -eq '0' ]]; then
-    echo "failed: failed to write access log"
-    exit 1
-fi
+  run curl -k -i -m 20 -o /dev/null -s -w %{http_code} --resolve 'www.test2.com:9001:127.0.0.1' "https://www.test2.com:9001/apisix/admin/tool/version"
+  [ "$output" -eq 200 ]
 
-# clean config
-clean_up
+  stop_dashboard 6
+}
 
-# set ip allowed list
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" 's@- 127.0.0.1 @- 10.0.0.1 @' conf/conf.yaml
-else
-  sed -i 's@- 127.0.0.1 @- 10.0.0.1 @' conf/conf.yaml
-fi
+#10
+@test "Check etcd basic auth" {
+  recover_conf
 
-./manager-api &
-sleep 3
+  # add root user
+  curl -L http://localhost:2379/v3/auth/user/add -d '{"name": "root", "password": "root"}'
 
-# should be forbidden
-curl http://127.0.0.1:9000
-code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9000)
-if [ ! $code -eq 403 ]; then
-    echo "failed: verify IP allowed list failed"
-    exit 1
-fi
+  # add root role
+  curl -L http://localhost:2379/v3/auth/role/add -d '{"name": "root"}'
 
-./manager-api stop
-sleep 6
-clean_up
+  # grant root role to root user
+  curl -L http://localhost:2379/v3/auth/user/grant -d '{"user": "root", "role": "root"}'
 
+  # enable auth
+  curl -L http://localhost:2379/v3/auth/enable -d '{}'
 
-# HTTPS test
-currentDir=$(pwd)
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" 's@# ssl:@ssl:@' conf/conf.yaml
-  sed -i "" 's@#   port: 9001@  port: 9001@' conf/conf.yaml
-  sed -i "" "s@#   cert: \"/tmp/cert/example.crt\"@  cert: \"$currentDir/test/certs/test2.crt\"@" conf/conf.yaml
-  sed -i "" "s@#   key:  \"/tmp/cert/example.key\"@  cert: \"$currentDir/test/certs/test2.key\"@" conf/conf.yaml
-else
-  sed -i 's@# ssl:@ssl:@' conf/conf.yaml
-  sed -i 's@#   port: 9001@  port: 9001@' conf/conf.yaml
-  sed -i "s@#   cert: \"/tmp/cert/example.crt\"@  cert: \"$currentDir/test/certs/test2.crt\"@" conf/conf.yaml
-  sed -i "s@#   key:  \"/tmp/cert/example.key\"@  key: \"$currentDir/test/certs/test2.key\"@" conf/conf.yaml
-fi
+  start_dashboard 3
 
-./manager-api &
-sleep 3
+  [ $(grep -c "etcdserver: user name is empty" ${LOG_FILE}) -ne '0' ]
 
-# access by HTTPS
-code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} --resolve 'www.test2.com:9001:127.0.0.1' https://www.test2.com:9001/apisix/admin/tool/version)
-if [ ! $code -eq 200 ]; then
-    echo "failed: verify HTTPS failed"
-    exit 1
-fi
+  stop_dashboard 6
 
-./manager-api stop
-sleep 6
-clean_up
+  # modify etcd auth config
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" '1,$s/# username: "root"    # ignore etcd username if not enable etcd auth/username: "root"/g' ${CONF_FILE}
+    sed -i "" '1,$s/# password: "123456"  # ignore etcd password if not enable etcd auth/password: "root"/g' ${CONF_FILE}
+  else
+    sed -i '1,$s/# username: "root"    # ignore etcd username if not enable etcd auth/username: "root"/g' ${CONF_FILE}
+    sed -i '1,$s/# password: "123456"  # ignore etcd password if not enable etcd auth/password: "root"/g' ${CONF_FILE}
+  fi
 
+  start_dashboard 3
 
-# etcd basic auth
-# add root user
-curl -L http://localhost:2379/v3/auth/user/add -d '{"name": "root", "password": "root"}'
+  # validate process is right by requesting login api
+  run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
 
-# add root role
-curl -L http://localhost:2379/v3/auth/role/add -d '{"name": "root"}'
+  [ -n "${token}" ]
 
-# grant root role to root user
-curl -L http://localhost:2379/v3/auth/user/grant -d '{"user": "root", "role": "root"}'
+  # more validation to make sure it's ok to access etcd
+  run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_basic_auth_test"}'
+  respCode=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d')
+  respMessage=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d')
 
-# enable auth
-curl -L http://localhost:2379/v3/auth/enable -d '{}'
+  [ "$respCode" = "0" ]
+  [ "$respMessage" = "\"\"" ]
 
-./manager-api &
-sleep 3
+  run curl "http://127.0.0.1:9000/apisix/admin/tool/version"
 
-# make sure it's wrong
-if [[ `grep -c "etcdserver: user name is empty" ${logfile}` -eq '0' ]]; then
-    echo "failed: failed to validate etcd basic auth"
-    exit 1
-fi
+  [ $(echo "$output" | grep -c "${VERSION}") -eq '1' ]
+  [ $(echo "$output" | grep -c "${GITHASH}") -eq '1' ]
 
-./manager-api stop
-sleep 6
+  check_logfile
 
-# modify etcd auth config
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" '1,$s/# username: "root"    # ignore etcd username if not enable etcd auth/username: "root"/g' conf/conf.yaml
-  sed -i "" '1,$s/# password: "123456"  # ignore etcd password if not enable etcd auth/password: "root"/g' conf/conf.yaml
-else
-  sed -i '1,$s/# username: "root"    # ignore etcd username if not enable etcd auth/username: "root"/g' conf/conf.yaml
-  sed -i '1,$s/# password: "123456"  # ignore etcd password if not enable etcd auth/password: "root"/g' conf/conf.yaml
-fi
+  stop_dashboard 6
 
-./manager-api &
-sleep 3
+  # disable etcd basic auth
+  run curl -L http://localhost:2379/v3/auth/authenticate -X POST -d '{"name": "root", "password": "root"}'
+  etcd_token=$(echo "$output" |grep -oE "token\".*\"(.*)\""|awk -F[:\"] '{print $4}')
+  [ -n "${etcd_token}" ]
 
-# validate process is right by requesting login api
-resp=$(curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}')
-token=$(echo "${resp}" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
-if [ -z "${token}" ]; then
-    echo "login failed"
-    exit 1
-fi
+  run curl -L http://localhost:2379/v3/auth/disable -H "Authorization: ${etcd_token}" -X POST -d ''
+  [ "$status" -eq 0 ]
+}
 
-# more validation to make sure it's ok to access etcd
-resp=$(curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_basic_auth_test"}')
-respCode=$(echo "${resp}" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d')
-respMessage=$(echo "${resp}" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d')
-if [ "$respCode" != "0" ] || [ $respMessage != "\"\"" ]; then
-    echo "verify access etcd failed"
-    exit 1
-fi
+#11
+@test "Check etcd prefix" {
+  recover_conf
 
-# check the version api
-resp=$(curl http://127.0.0.1:9000/apisix/admin/tool/version)
-if [[ `echo ${resp} | grep -c "${VERSION}"` -ne '1' ]]; then
-    echo "failed: can't through api to get version info"
-    exit 1
-fi
+  start_dashboard 3
 
-if [[ `echo ${resp} | grep -c "${GITHASH}"` -ne '1' ]]; then
-    echo "failed: can't through api to get githash info"
-    exit 1
-fi
+  run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  [ "$status" -eq 0 ]
 
-check_logfile
+  token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
+  [ -n "${token}" ]
 
-./manager-api stop
-sleep 6
-clean_up
+  prefix="/apisix"
+  key_base64=$(echo -n $prefix/consumers/etcd_prefix_test | base64)
 
-# etcd prefix test
-# disable etcd authentication
-resp=$(curl -L http://localhost:2379/v3/auth/authenticate -X POST -d '{"name": "root", "password": "root"}')
-etcd_token=$(echo ${resp}|grep -oE "token\".*\"(.*)\""|awk -F[:\"] '{print $4}')
-if [ -z "${etcd_token}" ]; then
-    echo "authenticate failed"
-    exit 1
-fi
-curl -L http://localhost:2379/v3/auth/disable -H "Authorization: ${etcd_token}" -X POST -d ''
+  run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}'
+  [ "$status" -eq 0 ]
 
-./manager-api &
-sleep 3
+  run curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}'
+  [ "$status" -eq 0 ]
 
-resp=$(curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}')
-token=$(echo "${resp}" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
-if [ -z "${token}" ]; then
-    echo "login failed"
-    exit 1
-fi
-# default etcd prefix value /apisix
-prefix=/apisix
-# add consumer by manager-api
-resp=$(curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}')
-# get consumer by etcd v3 api
-key_base64=`echo -n $prefix/consumers/etcd_prefix_test | base64`
-resp=$(curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}')
-count=`echo $resp | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}'`
-if [ ! $count ] || [ $count -ne 1 ]; then
-    echo "consumer failed"
-    exit 1
-fi
+  count=$(echo "$output" | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}')
+  [ "$count" ]
+  [ "$count" -eq 1 ]
 
-./manager-api stop
-sleep 6
+  stop_dashboard 6
 
-clean_up
+  recover_conf
 
-# modify etcd prefix config to /apisix-test
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' conf/conf.yaml
-else
-  sed -i '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' conf/conf.yaml
-fi
+  # modify etcd prefix config to /apisix-test
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' ${CONF_FILE}
+  else
+    sed -i '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' ${CONF_FILE}
+  fi
 
-./manager-api &
-sleep 3
+  start_dashboard 3
 
-resp=$(curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}')
-token=$(echo "${resp}" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
-if [ -z "${token}" ]; then
-    echo "login failed"
-    exit 1
-fi
-# modified etcd prefix value /apisix-test
-prefix=/apisix-test
-# add consumer by manager-api
-resp=$(curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}')
-# get consumer by etcd v3 api
-key_base64=`echo -n $prefix/consumers/etcd_prefix_test | base64`
-resp=$(curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}')
-count=`echo $resp | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}'`
-if [ ! $count ] || [ $count -ne 1 ]; then
-    echo "consumer failed"
-    exit 1
-fi
+  run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  [ "$status" -eq 0 ]
 
-./manager-api stop
-sleep 6
-clean_up
+  token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
+  [ -n "${token}" ]
 
-# mtls test
-./etcd-v3.4.14-linux-amd64/etcd --name infra0 --data-dir infra0 \
-  --client-cert-auth --trusted-ca-file=$(pwd)/test/certs/mtls_ca.pem --cert-file=$(pwd)/test/certs/mtls_server.pem --key-file=$(pwd)/test/certs/mtls_server-key.pem \
-  --advertise-client-urls https://127.0.0.1:3379 --listen-client-urls https://127.0.0.1:3379 --listen-peer-urls http://127.0.0.1:3380 &
+  prefix="/apisix-test"
+  key_base64=$(echo -n $prefix/consumers/etcd_prefix_test | base64)
 
-currentDir=$(pwd)
+  run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}'
+  [ "$status" -eq 0 ]
 
-if [[ $KERNEL = "Darwin" ]]; then
-  sed -i "" "s@key_file: \"\"@key_file: \"$currentDir/test/certs/mtls_client-key.pem\"@g" conf/conf.yaml
-  sed -i "" "s@cert_file: \"\"@cert_file: \"$currentDir/test/certs/mtls_client.pem\"@g" conf/conf.yaml
-  sed -i "" "s@ca_file: \"\"@ca_file: \"$currentDir/test/certs/mtls_ca.pem\"@g" conf/conf.yaml
-  sed -i "" 's/127.0.0.1:2379/127.0.0.1:3379/' conf/conf.yaml
-else
-  sed -i "s@key_file: \"\"@key_file: \"$currentDir/test/certs/mtls_client-key.pem\"@g" conf/conf.yaml
-  sed -i "s@cert_file: \"\"@cert_file: \"$currentDir/test/certs/mtls_client.pem\"@g" conf/conf.yaml
-  sed -i "s@ca_file: \"\"@ca_file: \"$currentDir/test/certs/mtls_ca.pem\"@g" conf/conf.yaml
-  sed -i 's/127.0.0.1:2379/127.0.0.1:3379/' conf/conf.yaml
-fi
+  run curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}'
+  [ "$status" -eq 0 ]
 
-./manager-api &
-sleep 3
+  count=$(echo "$output" | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}')
+  [ "$count" ]
+  [ "$count" -eq 1 ]
 
-# validate process is right by requesting login api
-resp=$(curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}')
-token=$(echo "${resp}" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
-if [ -z "${token}" ]; then
-    echo "login failed(mTLS connect to ETCD)"
-    exit 1
-fi
+  stop_dashboard 6
+}
 
-# more validation to make sure it's ok to access etcd
-resp=$(curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_basic_auth_test"}')
-respCode=$(echo "${resp}" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d')
-respMessage=$(echo "${resp}" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d')
-if [ "$respCode" != "0" ] || [ $respMessage != "\"\"" ]; then
-    echo "verify writing data failed(mTLS connect to ETCD)"
-    exit 1
-fi
+#12
+@test "Check etcd mTLS" {
+  recover_conf
 
-./manager-api stop
-sleep 6
-clean_up
+  run ./etcd-v3.4.14-linux-amd64/etcd --name infra0 --data-dir infra0 \
+        --client-cert-auth --trusted-ca-file=$(pwd)/test/certs/mtls_ca.pem --cert-file=$(pwd)/test/certs/mtls_server.pem --key-file=$(pwd)/test/certs/mtls_server-key.pem \
+        --advertise-client-urls https://127.0.0.1:3379 --listen-client-urls https://127.0.0.1:3379 --listen-peer-urls http://127.0.0.1:3380 &
 
-# run manager api as os service
-# 2 times OK for installing and starting
-if [[ `echo $(sudo ./manager-api start) | grep -o "OK" | wc -l` -ne "2" ]]; then
-  echo "error while initializing the service"
-  exit 1
-fi
-# check running status
-if [[ `echo $(sudo ./manager-api status) | grep -c "running..."` -ne "1" ]]; then
-  echo "error while starting the service"
-  exit 1
-fi
-# stop the service
-sudo ./manager-api stop
-sleep 2
-# recheck running status
-if [[ `echo $(sudo ./manager-api status) | grep -c "Service is stopped"` -ne "1" ]]; then
-  echo "error while stopping the service"
-  exit 1
-fi
-# restart the service
-# 1 time OK for just for starting
-if [[ `echo $(sudo ./manager-api start) | grep -c "OK"` -ne "1" ]]; then
-  echo "error while restarting the service"
-  exit 1
-fi
-# stop the service
-sudo ./manager-api stop
-sleep 2
-# remove the service
-if [[ `echo $(sudo ./manager-api remove) | grep -c "OK"` -ne "1" ]]; then
-  echo "error while removing the service"
-  exit 1
-fi
-# test manager-api output for bad data on etcd
-# make a dummy entry
-./etcd-v3.4.14-linux-amd64/etcdctl put /apisix/routes/unique1 "{\"id\":}"
-sleep 2
-
-./manager-api 2>man-api.err &
-sleep 4
-
-if [[ `cat man-api.err | grep -c "Error occurred while initializing logical store:  /apisix/routes"` -ne '1' ||
-`cat man-api.err | grep -c "Error: json unmarshal failed"` -ne '1' ]];then
-  echo "manager api failed to stream error on stderr for bad data"
-  exit 1
-fi
-# delete dummy entry
-./etcd-v3.4.14-linux-amd64/etcdctl del /apisix/routes/unique1
-# just to make sure
-./manager-api stop
-sleep 6
-clean_up
-
-# run manager api as os service
-# 2 times OK for installing and starting
-if [[ `echo $(sudo ./manager-api start) | grep -o "OK" | wc -l` -ne "2" ]]; then
-  echo "error while initializing the service"
-  exit 1
-fi
-# check running status
-if [[ `echo $(sudo ./manager-api status) | grep -c "running..."` -ne "1" ]]; then
-  echo "error while starting the service"
-  exit 1
-fi
-# stop the service
-sudo ./manager-api stop
-sleep 2
-# recheck running status
-if [[ `echo $(sudo ./manager-api status) | grep -c "Service is stopped"` -ne "1" ]]; then
-  echo "error while stopping the service"
-  exit 1
-fi
-# restart the service
-# 1 time OK for just for starting
-if [[ `echo $(sudo ./manager-api start) | grep -c "OK"` -ne "1" ]]; then
-  echo "error while restarting the service"
-  exit 1
-fi
-# stop the service
-sudo ./manager-api stop
-sleep 2
-# remove the service
-if [[ `echo $(sudo ./manager-api remove) | grep -c "OK"` -ne "1" ]]; then
-  echo "error while removing the service"
-  exit 1
-fi
+  if [[ $KERNEL = "Darwin" ]]; then
+    sed -i "" "s@key_file: \"\"@key_file: \"$(pwd)/test/certs/mtls_client-key.pem\"@g" ${CONF_FILE}
+    sed -i "" "s@cert_file: \"\"@cert_file: \"$(pwd)/test/certs/mtls_client.pem\"@g" ${CONF_FILE}
+    sed -i "" "s@ca_file: \"\"@ca_file: \"$(pwd)/test/certs/mtls_ca.pem\"@g" ${CONF_FILE}
+    sed -i "" 's/127.0.0.1:2379/127.0.0.1:3379/' ${CONF_FILE}
+  else
+    sed -i "s@key_file: \"\"@key_file: \"$(pwd)/test/certs/mtls_client-key.pem\"@g" ${CONF_FILE}
+    sed -i "s@cert_file: \"\"@cert_file: \"$(pwd)/test/certs/mtls_client.pem\"@g" ${CONF_FILE}
+    sed -i "s@ca_file: \"\"@ca_file: \"$(pwd)/test/certs/mtls_ca.pem\"@g" ${CONF_FILE}
+    sed -i 's/127.0.0.1:2379/127.0.0.1:3379/' ${CONF_FILE}
+  fi
+
+  start_dashboard 3
+
+  run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}'
+  [ "$status" -eq 0 ]
+
+  token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g'  | sed 's/"//g')
+  [ -n "${token}" ]
 
-pkill -f etcd
+  run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_mtls_test"}'
+  respCode=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d')
+  respMessage=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d')
 
-clean_up
+  [ "$respCode" = "0" ]
+  [ "$respMessage" = "\"\"" ]
+
+  stop_dashboard 6
+}
+
+#13
+@test "Check etcd bad data" {
+  recover_conf
+
+  run ./etcd-v3.4.14-linux-amd64/etcdctl put /apisix/routes/unique1 "{\"id\":}"
+  [ "$status" -eq 0 ]
+  sleep 2
+
+  start_dashboard 3
+
+  run journalctl -u ${SERVICE_NAME}.service -n 30
+
+  [ $(echo "$output" | grep -c "Error occurred while initializing logical store:  /apisix/routes") -eq '1' ]
+  [ $(echo "$output" | grep -c "Error: json unmarshal failed") -eq '1' ]
+
+  run ./etcd-v3.4.14-linux-amd64/etcdctl del /apisix/routes/unique1
+  [ "$status" -eq 0 ]
+
+  stop_dashboard 6
+}
+
+#post
+@test "Clean test environment" {
+  # kill etcd
+  pkill -f etcd
+
+  # stop dashboard service
+  stop_dashboard 0
+
+  # clean configure and log files
+  rm -rf /usr/local/apisix-dashboard
+  rm /usr/lib/systemd/system/${SERVICE_NAME}.service
+
+  # reload systemd services
+  run systemctl daemon-reload
+  [ "$status" -eq 0 ]
+}
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 73bbd82..9c1869d 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -9,7 +9,7 @@
     {
       "type": "category",
       "label": "Installation",
-      "items": ["deploy", "deploy-with-docker", "deploy-with-rpm"]
+      "items": ["install", "deploy-with-docker"]
     },
     {
       "type": "category",
diff --git a/docs/en/latest/deploy-with-docker.md b/docs/en/latest/deploy-with-docker.md
index fde965d..704d244 100644
--- a/docs/en/latest/deploy-with-docker.md
+++ b/docs/en/latest/deploy-with-docker.md
@@ -1,5 +1,5 @@
 ---
-title: Deploy with Docker
+title: Rebuild Docker image
 ---
 
 <!--
diff --git a/docs/en/latest/deploy-with-rpm.md b/docs/en/latest/deploy-with-rpm.md
deleted file mode 100644
index 49d1308..0000000
--- a/docs/en/latest/deploy-with-rpm.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: Deploy with RPM
----
-
-<!--
-#
-# 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.
-#
--->
-
-**NOTE:** Only support CentOS 7 currently, for more information, please refer to [here](./deploy.md).
-
-## Install from RPM
-
-```sh
-$ sudo yum install -y https://github.com/apache/apisix-dashboard/releases/download/v2.9.0/apisix-dashboard-2.9.0-0.el7.x86_64.rpm
-```
-
-## Run
-
-Before you start, make sure the following dependencies are installed and running in your environment.
-
-- [etcd](https://etcd.io/docs/v3.4.0/dl-build/) 3.4.0+
-
-```sh
-$ sudo nohup manager-api -p /usr/local/apisix/dashboard/ &
-
-# or manager-api as a service
-$ sudo manager-api start -p /usr/local/apisix/dashboard/
-```
-
-Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`.
diff --git a/docs/en/latest/deploy.md b/docs/en/latest/deploy.md
deleted file mode 100644
index 47b7f51..0000000
--- a/docs/en/latest/deploy.md
+++ /dev/null
@@ -1,133 +0,0 @@
----
-title: Deploy with Source Codes
----
-
-<!--
-#
-# 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.
-#
--->
-
-The Dashboard contains both `manager-api` and `web`, but `web` is _optional_.
-
-The `manager-api` and `web` will be included in this build guide product.
-
-## Prerequisites
-
-Before using source codes to build, make sure that the following dependencies are installed in your environment.
-
-### manager-api
-
-1. [Golang](https://golang.org/dl/) 1.13+: For users in mainland China, you can use the following command to speed up the module downloads.
-
-```sh
-$ go env -w GOPROXY=https://goproxy.cn,direct
-```
-
-### web
-
-1. [Node.js](https://nodejs.org/en/download/) 10.23.0+
-2. [Yarn](https://yarnpkg.com/getting-started/install)
-
-## Clone the project
-
-```sh
-$ git clone -b release/2.9.0 https://github.com/apache/apisix-dashboard.git
-```
-
-## Build
-
-```sh
-$ cd apisix-dashboard
-$ make build
-```
-
-When the build is complete, the results are stored in the root `output` directory.
-
-Note: `make build` will build `manager-api` and `web`, use the `make help` command to see more commands.
-
-## Launch
-
-1. After the build is complete and before you start, make sure the following dependencies are installed and running in your environment.
-
-- [etcd](https://etcd.io/docs/v3.4.0/dl-build/) 3.4.0+
-
-2. Check and modify the configuration information in `output/conf/conf.yaml` according to your deployment environment.
-
-3. Launch the Dashboard
-
-```sh
-$ cd ./output
-
-$ ./manager-api
-
-# or running in background
-$ nohup ./manager-api &
-```
-
-4. Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`.
-
-5. Stop the Dashboard
-
-`manager-api` provides a sub command `stop` to quit the program gracefully, just
-run:
-
-```sh
-$ ./manager-api stop
-```
-
-### Optional
-
-6. Running `manager-api` as an OS service.
-
-Without clubbing `manager-api` with external command such as `nohup` (in Unix systems), we also provide a long term solution for running the program as an **operating system managed background service**. The feature is cross-platform, os agnostic and works on well known Linux, Windows and Mac OS distributions.
-
-**Note:** The `manager-api` as a service is listed by the name `apisix-dashboard` under the OS's service manager.
-
-`manager-api` provides a list of sub commands to start and manage the lifecycle of the background service.
-
-```sh
-# start Apache APISIX Dashboard service
-$ ./manager-api start
-
-# re-install Apache APISIX Dashboard service
-$ ./manager-api install
-
-# inspect the status of Apache APISIX Dashboard service
-$ ./manager-api status
-
-# stop Apache APISIX Dashboard service. stop can be used with or without service
-$ ./manager-api stop
-
-# remove Apache APISIX Dashboard service
-$ ./manager-api remove
-```
-
-## Working directory
-
-The `output` directory mention above is the default working directory.
-
-You can move the entire directory to any path you want, and use the `-p` to specify it as the working directory.
-
-For example, you can move it to `/usr/local/apisix-dashboard/`
-
-```sh
-$ mv ./output/manager-api /usr/local/bin/
-
-$ mv ./output/ /usr/local/apisix-dashboard/
-
-$ manager-api -p /usr/local/apisix-dashboard/
-```
diff --git a/docs/en/latest/install.md b/docs/en/latest/install.md
new file mode 100644
index 0000000..bd3d110
--- /dev/null
+++ b/docs/en/latest/install.md
@@ -0,0 +1,170 @@
+---
+title: Basic Deploy
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+Installing Apache APISIX Dashboard on Linux is easy.
+Now, we provide Docker image and RPM installation package.
+
+## Docker {#docker}
+
+We recommend using Docker to run Dashboard:
+
+```shell
+docker pull apache/apisix-dashboard
+docker run -d --name dashboard \
+           -p 9000:9000        \
+           -v <CONFIG_FILE>:/usr/local/apisix-dashboard/conf/conf.yaml \
+           apache/apisix-dashboard
+```
+
+:::note
+Please replace `<CONFIG_FILE>` to your configure file path.
+:::
+
+## RPM {#rpm}
+
+**NOTE:** Only CentOS 7 is supported currently, for more information, please refer to [here](./deploy.md).
+
+### Install
+
+```shell
+# 1. install RPM package
+sudo yum install -y https://github.com/apache/apisix-dashboard/releases/download/v2.9.0/apisix-dashboard-2.9.0-0.el7.x86_64.rpm
+```
+
+### Launch
+
+```shell
+# run dashboard in the shell
+sudo manager-api -p /usr/local/apisix/dashboard/
+
+# or run dashboard as a service
+systemctl apisix-dashboard start
+```
+
+Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`.
+
+## Source {#source}
+
+The Dashboard project contains both `manager-api` and `web`, but `web` is _optional_.
+
+The `manager-api` and `web` will be included in this build guide product.
+
+### Prerequisites {#source-prerequisites}
+
+Before using source codes to build, make sure that the following dependencies are installed in your environment.
+
+For `manager-api`:
+
+1. [Golang](https://golang.org/dl/) 1.13+
+
+> Tip: For users in mainland China, you can use the following command to speed up the module downloads.
+
+```sh
+$ go env -w GOPROXY=https://goproxy.cn,direct
+```
+
+For `web`:
+
+1. [Node.js](https://nodejs.org/en/download/) current LTS (14.x+)
+2. [Yarn](https://yarnpkg.com/getting-started/install)
+
+### Download {#source-download}
+
+```shell
+git clone -b release/2.9.0 https://github.com/apache/apisix-dashboard.git && cd apisix-dashboard
+```
+
+### Build {#source-build}
+
+```shell
+cd apisix-dashboard
+make build
+```
+
+When the build is complete, the results are stored in the root `output` directory.
+
+Note: `make build` will build `manager-api` and `web`, use the `make help` command to see more commands.
+
+### Launch {#source-launch}
+
+1. After the build is complete and before you start, make sure the following dependencies are installed and running in your environment.
+
+- [etcd](https://etcd.io/docs/v3.4.0/dl-build/) 3.4.0+
+
+2. Check and modify the configuration information in `output/conf/conf.yaml` according to your deployment environment.
+
+3. Launch the Dashboard
+
+```shell
+cd ./output
+
+./manager-api
+```
+
+4. Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`.
+
+### Service {#source-service}
+
+You will need to handle your own service management when deploying using the source code compilation method. We provide a service file template for operating systems that use the Systemd service manager.
+
+1. Install
+
+```shell
+mkdir -p /usr/local/apisix-dashboard
+cp -rf ./output/* /usr/local/apisix-dashboard
+```
+
+2. Create service unit
+
+Copy the following or use this [**file**](https://github.com/apache/apisix-dashboard/tree/master/service/apisix-dashboard.service) directly, you need to copy it to the `/usr/lib/systemd/system` directory and execute the `systemctl daemon-reload` command.
+
+```shell
+# copy service unit
+cp ./api/service/apisix-dashboard.service /usr/lib/systemd/system/apisix-dashboard.service
+systemctl daemon-reload
+
+# or: If you need to modify the service unit, you can use the following command
+echo "[Unit]
+Description=apisix-dashboard
+Conflicts=apisix-dashboard.service
+After=network-online.target
+
+[Service]
+WorkingDirectory=/usr/local/apisix-dashboard
+ExecStart=/usr/local/apisix-dashboard/manager-api -c /usr/local/apisix-dashboard/conf/conf.yaml" > /usr/lib/systemd/system/apisix-dashboard.service
+```
+
+3. Manage service
+
+You can use the following command to manage the service.
+
+```shell
+# start apisix-dashboard
+systemctl start apisix-dashboard
+
+# stop apisix-dashboard
+systemctl stop apisix-dashboard
+
+# check apisix-dashboard status
+systemctl status apisix-dashboard
+```