Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
varnishapi
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
uplex-varnish
varnishapi
Commits
6af60ff6
Commit
6af60ff6
authored
Aug 24, 2018
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add package admin.
parent
765d4f8b
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1045 additions
and
0 deletions
+1045
-0
admin.go
pkg/admin/admin.go
+518
-0
cli.go
pkg/admin/cli.go
+229
-0
e2e_test.go
pkg/admin/e2e_test.go
+298
-0
No files found.
pkg/admin/admin.go
0 → 100644
View file @
6af60ff6
/*-
* 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
}
pkg/admin/cli.go
0 → 100644
View file @
6af60ff6
/*-
* 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
}
pkg/admin/e2e_test.go
0 → 100644
View file @
6af60ff6
// +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"
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment