Commit 6af60ff6 authored by Geoff Simmons's avatar Geoff Simmons

Add package admin.

parent 765d4f8b
This diff is collapsed.
/*-
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package admin
import (
"errors"
"strconv"
"strings"
"time"
)
// Pong represents the response to the "ping" command.
type Pong struct {
Time time.Time
CLIVersion string
}
// Ping encapsulates the "ping" command. error is non-nil when the
// response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
func (adm *Admin) Ping() (Pong, error) {
pong := Pong{}
resp, err := adm.Command("ping")
if err != nil {
return pong, err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return pong, badResp
}
answer := strings.Split(resp.Msg, " ")
epoch, err := strconv.Atoi(answer[1])
if err != nil {
return pong, err
}
pong.Time = time.Unix(int64(epoch), 0)
pong.CLIVersion = answer[2]
return pong, err
}
// A State represents the status of the Varnish child process.
type State uint8
const (
// Stopped indicates that the child was stopped regularly.
Stopped State = iota
// Starting indicates that the management process has begun
// but not completed starting the child process.
Starting
// Running indicates that the child process is running
// regularly.
Running
// Stopping indicates that the management process is shutting
// down the child process regularly.
Stopping
// Died indicates that the child process process stopped
// unexpectedly, and will be restarted by the management
// process.
Died
)
func (state State) String() string {
switch state {
case Stopped:
return "stopped"
case Starting:
return "starting"
case Running:
return "running"
case Stopping:
return "stopping"
case Died:
return "died unexpectedly (being restarted)"
default:
panic("Invalid state")
}
}
// Status encapsulates the "status" command. error is non-nil when the
// response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
func (adm *Admin) Status() (State, error) {
resp, err := adm.Command("status")
if err != nil {
return Died, err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return Died, badResp
}
if strings.Contains(resp.Msg, "running") {
return Running, nil
}
if strings.Contains(resp.Msg, "stopped") {
return Stopped, nil
}
if strings.Contains(resp.Msg, "starting") {
return Starting, nil
}
if strings.Contains(resp.Msg, "stopping") {
return Stopping, nil
}
if strings.Contains(resp.Msg, "died") {
return Died, nil
}
return Died, errors.New("unknown status")
}
// Start encapsulates the "start" command. error is non-nil when the
// response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
func (adm *Admin) Start() error {
resp, err := adm.Command("start")
if err != nil {
return err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return badResp
}
return nil
}
// Stop encapsulates the "stop" command. error is non-nil when the
// response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
func (adm *Admin) Stop() error {
resp, err := adm.Command("stop")
if err != nil {
return err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return badResp
}
return nil
}
// VCLLoad encapsulates the "vcl.load" command. error is non-nil when
// the response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
//
// Varnish usually returns status 106 (Param) when the load fails due
// to a compile failure, and the error message is in the wrapped
// Response.Msg.
//
// config is the configuration name under which the VCL instance can
// be referenced afterward. The name may not be already in use. path
// is file path at which the VCL source is located.
func (adm *Admin) VCLLoad(config string, path string) error {
resp, err := adm.Command("vcl.load", config, path)
if err != nil {
return err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return badResp
}
return nil
}
// VCLUse encapsulates the "vcl.use" command. error is non-nil when
// the response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
//
// config is the configuration name previousy set with VCLLoad(), or a
// previous invocation of "vcl.load". On success, that config becomes
// the active VCL instance.
func (adm *Admin) VCLUse(config string) error {
resp, err := adm.Command("vcl.use", config)
if err != nil {
return err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return badResp
}
return nil
}
// VCLDiscard encapsulates the "vcl.discard" command. error is non-nil
// when the response was not OK. In that case, the error is an
// UnexpectedResponse, which wraps the response from Varnish.
//
// config is the configuration name previousy set with VCLLoad(), or a
// previous invocation of "vcl.load". The config may not be the
// current active config. On success, that config is no longer
// available, and cannot become active via VCLUse() or "vcl.use".
func (adm *Admin) VCLDiscard(config string) error {
resp, err := adm.Command("vcl.discard", config)
if err != nil {
return err
}
if resp.Code != OK {
badResp := UnexpectedResponse{Response: resp}
return badResp
}
return nil
}
// +build e2e
/*-
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
// This test is only run if go test is invoked with a -tags option
// that includes "e2e". It requires that two instances of Varnish are
// running: a default instance (varnishd invoked without the -n
// option), and an instance named "gotest" (varnishd -n "gotest"). For
// the purposes of this test, it does not matter how either instance
// is configured, only that they are running.
package admin
import (
"uplex.de/varnishapi/internal/pkg/vsm"
"io/ioutil"
"os"
"strings"
"testing"
"time"
)
var (
address string
secret []byte
)
func TestMain(m *testing.M) {
vsm := vsm.New()
if vsm == nil {
panic("Cannot open VSM")
}
defer vsm.Destroy()
if err := vsm.Attach(""); err != nil {
panic(err)
}
targ, err := vsm.Get("Arg", "-T")
if err != nil {
panic(err)
}
targ = strings.TrimSpace(targ)
address = strings.Replace(targ, " ", ":", 1)
sarg, err := vsm.Get("Arg", "-S")
if err != nil {
panic(err)
}
sarg = strings.TrimSpace(sarg)
sfile, err := os.Open(sarg)
if err != nil {
panic(err)
}
secret, err = ioutil.ReadAll(sfile)
if err != nil {
panic(err)
}
os.Exit(m.Run())
}
func TestAttach(t *testing.T) {
adm, err := Attach("", time.Duration(0))
if err != nil {
t.Fatal("Attach():", err)
return
}
defer adm.Close()
if adm.Banner == "" {
t.Error("Banner is empty after Attach()")
}
if !strings.Contains(adm.Banner, "Varnish Cache CLI") {
t.Error("Banner does not contain 'Varnish Cache CLI'")
}
tadm, err := Attach("gotest", time.Duration(0))
if err != nil {
t.Fatal("Attach(gotest):", err)
return
}
defer tadm.Close()
if tadm.Banner == "" {
t.Error("Banner is empty after Attach()")
}
if !strings.Contains(adm.Banner, "Varnish Cache CLI") {
t.Error("Banner does not contain 'Varnish Cache CLI'")
}
}
func TestDial(t *testing.T) {
adm, err := Dial(address, secret, time.Duration(0))
if err != nil {
t.Fatal("Dial():", err)
return
}
defer adm.Close()
if adm.Banner == "" {
t.Error("Banner is empty after Attach()")
}
if !strings.Contains(adm.Banner, "Varnish Cache CLI") {
t.Error("Banner does not contain 'Varnish Cache CLI'")
}
}
func TestListen(t *testing.T) {
adm, err := Listen("127.0.0.1:0")
if err != nil {
t.Fatal("Listen():", err)
return
}
defer adm.Close()
}
func TestClose(t *testing.T) {
adm, err := Dial(address, secret, time.Duration(0))
if err != nil {
t.Fatal("Dial():", err)
return
}
if err = adm.Close(); err != nil {
t.Error("Close():", err)
}
var n *Admin
if err = n.Close(); err == nil {
t.Error("Expected nil.Close() to fail")
}
}
func TestLocalAddr(t *testing.T) {
adm, err := Dial(address, secret, time.Duration(0))
if err != nil {
t.Fatal("Dial():", err)
return
}
defer adm.Close()
addr, err := adm.LocalAddr()
if err != nil {
t.Fatal("LocalAddr():", err)
return
}
if addr.Network() != "tcp" {
t.Errorf("LocalAddr().Network() want=tcp got=%v",
addr.Network())
}
str := addr.String()
if str == "" {
t.Error("LocalAddr().String() is empty")
}
var n *Admin
if addr, err = n.LocalAddr(); err == nil {
t.Error("Expected nil.LocalAddr() to fail")
}
unconn := new(Admin)
if addr, err = unconn.LocalAddr(); err == nil {
t.Error("Expected unconnected.LocalAddr() to fail")
}
}
func TestRemoteAddr(t *testing.T) {
adm, err := Dial(address, secret, time.Duration(0))
if err != nil {
t.Fatal("Dial():", err)
return
}
defer adm.Close()
addr, err := adm.RemoteAddr()
if err != nil {
t.Fatal("RemoteAddr():", err)
return
}
if addr.Network() != "tcp" {
t.Errorf("RemoteAddr().Network() want=tcp got=%v",
addr.Network())
}
str := addr.String()
if str == "" {
t.Error("RemoteAddr().String() is empty")
}
if str != address {
t.Errorf("RemoteAddr().String() want=%v got=%v", address, str)
}
var n *Admin
if addr, err = n.RemoteAddr(); err == nil {
t.Error("Expected nil.RemoteAddr() to fail")
}
unconn := new(Admin)
if addr, err = unconn.RemoteAddr(); err == nil {
t.Error("Expected unconnected.RemoteAddr() to fail")
}
}
func TestListenerAddr(t *testing.T) {
adm, err := Listen("127.0.0.1:0")
if err != nil {
t.Fatal("Listen():", err)
return
}
defer adm.Close()
addr, err := adm.ListenerAddr()
if err != nil {
t.Fatal("ListenerAddr():", err)
return
}
if addr.Network() != "tcp" {
t.Errorf("ListenerAddr().Network() want=tcp got=%v",
addr.Network())
}
str := addr.String()
if str == "" {
t.Error("ListenerAddr().String() is empty")
}
var n *Admin
if addr, err = n.ListenerAddr(); err == nil {
t.Error("Expected nil.ListenerAddr() to fail")
}
unconn := new(Admin)
if addr, err = unconn.ListenerAddr(); err == nil {
t.Error("Expected unconnected.ListenerAddr() to fail")
}
}
func TestCommand(t *testing.T) {
adm, err := Dial(address, secret, time.Duration(0))
if err != nil {
t.Fatal("Dial():", err)
return
}
defer adm.Close()
resp, err := adm.Command("banner")
if err != nil {
t.Fatal("Command():", err)
return
}
if resp.Msg == "" {
t.Error("resp.Msg after Command() is empty")
}
if resp.Msg != adm.Banner {
t.Errorf("resp.Msg after Command(banner) want=%v got=%v",
resp.Msg, adm.Banner)
}
if resp.Code < 100 || resp.Code > 999 {
t.Errorf("Expected 100 <= resp.Code <= 999 got=%v", resp.Code)
}
for _, cmd := range []string{"ping", "status"} {
resp, err := adm.Command(cmd)
if err != nil {
t.Fatalf("Command(%s): %v", cmd, err)
return
}
if resp.Msg == "" {
t.Errorf("resp.Msg after Command(%s) is empty", cmd)
}
if resp.Code < 100 || resp.Code > 999 {
t.Errorf("Command(%s): Expected 100 <= resp.Code "+
"<= 999 got=%v", cmd, resp.Code)
}
}
var n *Admin
if resp, err = n.Command("banner"); err == nil {
t.Error("Expected nil.Command() to fail")
}
unconn := new(Admin)
if resp, err = unconn.Command("banner"); err == nil {
t.Error("Expected unconnected.Command() to fail")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment