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

Add package admin.

parent 765d4f8b
/*-
* 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 implements a client for the Varnish administrative
// interface, or CLI. An Admin client establishes a network connection
// with the port at which the Varnish management process accepts
// administrative commands, and implements the protocol documented in
// varnish-cli(7). The commands in the CLI should be familiar to users
// of varnishadm(1).
//
// The Command() method can be used to issue arbitrary commands. Some
// of the methods encapsulate CLI commands and parse the output,
// returning information as structured data.
//
// The CLI implements an authentication protocol that depends on a
// shared secret (see varnish-cli(7)). The secret data are in a file
// that is either set by the varnishd -S option, or generated
// automatically when Varnish starts (see varnishd(1)).
//
// Methods in this package that establish the CLI connection execute
// the authentication transparently, and require the secret in byte
// slice parameters. This data can be read from the file (for example
// with ioutil.ReadAll()), or client code can generate the secret and
// write the file, and then start Varnish with the -S option pointing
// to the file. Varnish reads the file anew when CLI authentication is
// necessary, so the file's contents can be changed at runtime.
package admin
import (
"uplex.de/varnishapi/internal/pkg/vsm"
"crypto/sha256"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strconv"
"strings"
"time"
)
const (
network = "tcp"
statusLen = 13
)
// A ResponseCode is an IETF-style 3-digit code that classifies the
// handling of an admin command.
type ResponseCode uint
const (
// Syntax error
Syntax ResponseCode = ResponseCode(100)
// Unknown command
Unknown = ResponseCode(101)
// Unimpl indicates that the command is reserved for a future
// implementation.
Unimpl = ResponseCode(102)
// TooFew indicates that there were not enough arguments for
// the command.
TooFew = ResponseCode(104)
// TooMany indicates that there too many arguments for the
// command.
TooMany = ResponseCode(105)
// Param indicates that there was an invalid command
// argument. This is the response for example when a VCL load
// fails due to a compile error, or when information about a
// runtime parameter was requested, but the parameter name
// does not exist.
Param = ResponseCode(106)
// Auth indicates that authentication is required to use the
// CLI. This should be handled transparently by the code in
// this package.
Auth = ResponseCode(107)
// OK indicates that the command was executed succesfully.
OK = ResponseCode(200)
// Truncated indicates that the command was executed
// succesfully, but the response text (in the Msg field of
// Response) was truncated.
Truncated = ResponseCode(201)
// Cant indicates that the command could not be executed, for
// example when the "start" command is issued, but the child
// process is already running.
Cant = ResponseCode(300)
// Comms indicates an error at the network or protocol level.
Comms = ResponseCode(400)
// Close indicates the Varnish is closing the admin
// connection.
Close = ResponseCode(500)
)
func (rc ResponseCode) String() string {
switch rc {
case Syntax:
return "Syntax error"
case Unknown:
return "Unknown command"
case Unimpl:
return "Command not implemented"
case TooFew:
return "Not enough arguments for command"
case TooMany:
return "Too many arguments for command"
case Param:
return "Illegal parameters"
case Auth:
return "Authentication required"
case OK:
return "OK"
case Truncated:
return "Response was truncated"
case Cant:
return "Unable to execute command"
case Comms:
return "Communication/protocol error"
case Close:
return "Closing connection"
default:
panic("Invalid response code")
}
}
// A Response is the response from Varnish to a command.
type Response struct {
Msg string
Code ResponseCode
}
// An UnexpectedResponse wraps a Response that indicates that a
// command produced an error. It implements the error interface.
type UnexpectedResponse struct {
Response Response
}
func (badResp UnexpectedResponse) Error() string {
return fmt.Sprintf("unexpected response %d (%s): %s",
badResp.Response.Code, badResp.Response.Code,
badResp.Response.Msg)
}
// An Admin is a client for the Vanrish administrative interface.
type Admin struct {
conn net.Conn
lsnr net.Listener
// The Banner text returned from Varnish when a CLI connection
// is established. This containes information about the
// Varnish version and some of its startup parameters.
Banner string
}
func read(conn net.Conn) (Response, error) {
resp := Response{}
if conn == nil {
return resp, errors.New("not connected")
}
bytes := make([]byte, statusLen)
if _, err := conn.Read(bytes); err != nil {
return resp, err
}
line := strings.TrimSpace(string(bytes))
status := strings.Split(line, " ")
code, err := strconv.Atoi(status[0])
if err != nil {
return resp, err
}
length, err := strconv.Atoi(status[1])
if err != nil {
return resp, err
}
bytes = make([]byte, length+1)
if _, err = conn.Read(bytes); err != nil {
return resp, err
}
resp.Msg = strings.TrimSpace(string(bytes))
resp.Code = ResponseCode(code)
return resp, nil
}
func auth(conn net.Conn, secret []byte) (Response, error) {
resp, err := read(conn)
if err != nil {
return resp, err
}
if resp.Code != Auth {
return resp, nil
}
challenge := resp.Msg[:strings.IndexByte(resp.Msg, '\n')+1]
h := sha256.New()
h.Write([]byte(challenge))
h.Write(secret)
h.Write([]byte(challenge))
sum := h.Sum([]byte(nil))
answer := fmt.Sprintf("auth %0x\n", sum)
if _, err := conn.Write([]byte(answer)); err != nil {
return resp, err
}
resp, err = read(conn)
if err != nil {
return resp, err
}
if resp.Code != OK {
return resp, errors.New("failed authentication")
}
return resp, nil
}
func (adm *Admin) checkNil() error {
if adm == nil {
return errors.New("Admin object is nil")
}
return nil
}
func (adm *Admin) checkConn() error {
if err := adm.checkNil(); err != nil {
return err
}
if adm.conn == nil {
return errors.New("Connection is uninitialized")
}
return nil
}
// Dial connects to the administrative port of a running Varnish
// management process (which may have been set by the varnishd -T
// option).
//
// addr is a string representing the address and port to which to
// connect, exactly as documented for net.Dial (the network class is
// implicitly "tcp"). timeout establishes a timeout for the
// connection, exactly as documented for net.DialTimeout.
//
// secret must contain the contents of the shared secret file, as
// documented above. Dial() implements the authentication
// transparently, and returns a non-nil error if it fails.
func Dial(addr string, secret []byte, timeout time.Duration) (*Admin, error) {
conn, err := net.DialTimeout(network, addr, timeout)
if err != nil {
return nil, err
}
resp, err := auth(conn, secret)
if err != nil {
return nil, err
}
if resp.Code != OK {
return nil, UnexpectedResponse{Response: resp}
}
return &Admin{
conn: conn,
Banner: resp.Msg,
}, nil
}
// Listen initializes a local address at which the client will listen
// for a connection from the Varnish management process, over which
// Varnish will offer the CLI. This can be used when varnishd is
// started with the -M option (see varnishd(1)).
//
// addr represents the address at which to listen, exactly as
// documented for net.Listen (the network class is implicitly "tcp").
func Listen(addr string) (*Admin, error) {
lsnr, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
return &Admin{
lsnr: lsnr,
}, nil
}
// Attach temporarily attaches to the shared memory of a running
// Varnish process, to discover the address at which Varnish accepts
// administrative commands, and the location of the secret file. This
// can be used when Varnish was started with neither of the -T or -S
// options. The shared memory attachment is released before Attach()
// finishes.
//
// Attach uses this information to connect to the admin port and
// execute authentication. On successful completion, the Admin client
// is connected to the CLI interface, just as when Dial() completes
// successfully.
//
// name identifies the instance of Varnish to which to connect. If
// name is empty, connect to the default instance (Varnish invoked
// without the -n option). Otherwise, connect to the instance
// identified by the value of varnishd -n (see varnishd(1)).
//
// timeout sets a timeout for the connection, as for Dial().
func Attach(name string, timeout time.Duration) (*Admin, error) {
vsm := vsm.New()
if vsm == nil {
return nil, errors.New("Cannot initialize for attachment")
}
defer vsm.Destroy()
if err := vsm.AttachTmo(timeout); err != nil {
return nil, err
}
if err := vsm.Attach(name); err != nil {
return nil, err
}
targ, err := vsm.Get("Arg", "-T")
if err != nil {
return nil, err
}
targ = strings.TrimSpace(targ)
targ = strings.Replace(targ, " ", ":", 1)
sarg, err := vsm.Get("Arg", "-S")
if err != nil {
return nil, err
}
sarg = strings.TrimSpace(sarg)
sfile, err := os.Open(sarg)
if err != nil {
return nil, err
}
secret, err := ioutil.ReadAll(sfile)
if err != nil {
return nil, err
}
adm, err := Dial(targ, secret, timeout)
if err != nil {
return nil, err
}
return adm, nil
}
// Accept accepts a connection from the Varnish management process,
// when the Admin instance was initialized with Listen() and Varnish
// was invoked with the -M option.
//
// secret must contain the contents of the shared secret file, as
// documented above. Accept() implements the authentication
// transparently, and returns a non-nil error if it fails.
//
// As with net.Accept, Accept() blocks until a connection has been
// accepted.
func (adm *Admin) Accept(secret []byte) error {
if err := adm.checkNil(); err != nil {
return err
}
if adm.lsnr == nil {
return errors.New("Listener is uninitialized")
}
conn, err := adm.lsnr.Accept()
if err != nil {
return err
}
resp, err := auth(conn, secret)
if err != nil {
return err
}
adm.conn = conn
adm.Banner = resp.Msg
return nil
}
// Close the admin connection (wisely used with defer after a
// connection has been established).
func (adm *Admin) Close() error {
if err := adm.checkNil(); err != nil {
return err
}
if adm.lsnr != nil {
if err := adm.lsnr.Close(); err != nil {
return err
}
}
if adm.conn != nil {
if err := adm.conn.Close(); err != nil {
return err
}
}
return nil
}
// LocalAddr returns a net.Addr representing the local address of an
// admin connection, if it was established with Dial() (see
// net.Conn.LocalAddr).
func (adm *Admin) LocalAddr() (net.Addr, error) {
if err := adm.checkConn(); err != nil {
return nil, err
}
return adm.conn.LocalAddr(), nil
}
// RemoteAddr returns a net.Addr representing the remote address of an
// admin connection (the address at which the Varnish management
// process is listening), if it was established with Dial() (see
// net.Conn.RemoteAddr).
func (adm *Admin) RemoteAddr() (net.Addr, error) {
if err := adm.checkConn(); err != nil {
return nil, err
}
return adm.conn.RemoteAddr(), nil
}
// ListenerAddr returns a net.Addr representing the address at which
// the client is listening for an admin connection, if it was
// initialized with Listen() (see net.Listener.Addr).
//
// This makes it possible, for example, to invoke Listen() with port 0
// for "any available port", then retrieve the address with the
// selected port, and start Varnish with the -M option to connect to
// that port.
func (adm *Admin) ListenerAddr() (net.Addr, error) {
if err := adm.checkNil(); err != nil {
return nil, err
}
if adm.lsnr == nil {
return nil, errors.New("Listener is uninitialized")
}
return adm.lsnr.Addr(), nil
}
// SetDeadline establishes read and write deadlines for the admin
// connection. See net.Conn.SetDeadline().
func (adm *Admin) SetDeadline(t time.Time) error {
if err := adm.checkConn(); err != nil {
return err
}
if err := adm.conn.SetDeadline(t); err != nil {
return err
}
return nil
}
// SetReadDeadline sets the deadline for reads on the admin
// connection. See net.Conn.SetReadDeadline().
func (adm *Admin) SetReadDeadline(t time.Time) error {
if err := adm.checkConn(); err != nil {
return err
}
if err := adm.conn.SetReadDeadline(t); err != nil {
return err
}
return nil
}
// SetWriteDeadline sets the deadline for writes on the admin
// connection. See net.Conn.SetWriteDeadline().
func (adm *Admin) SetWriteDeadline(t time.Time) error {
if err := adm.checkConn(); err != nil {
return err
}
if err := adm.conn.SetWriteDeadline(t); err != nil {
return err
}
return nil
}
// Command issues arbitrary commands to the admin interface, as
// documented in varnish-cli(7):
//
// resp, err := adm.Command("status")
// resp, err := adm.Command("vcl.load", "myconfig", "/path/to/my.vcl")
//
// Returns a non-nil error only if there was an error at the network
// level. If Varnish was able to handle the command at all
// (successfully or not), then the error is nil and the response from
// Varnish is encapsulated in the Response object, which then can be
// inspected to see if the command succeeded.
func (adm *Admin) Command(cmd ...string) (Response, error) {
resp := Response{}
if err := adm.checkConn(); err != nil {
return resp, err
}
cmd[len(cmd)-1] += "\n"
bytes := []byte(strings.Join(cmd, " "))
if _, err := adm.conn.Write(bytes); err != nil {
return resp, err
}
resp, err := read(adm.conn)
if err != nil {
return resp, err
}
return resp, nil
}
/*-
* 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