2015-09-06 00:06:05 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
2015-09-06 01:41:00 +00:00
|
|
|
"fmt"
|
2015-09-06 00:06:05 +00:00
|
|
|
"io/ioutil"
|
2016-08-16 23:05:37 +00:00
|
|
|
"net"
|
2015-09-06 00:06:05 +00:00
|
|
|
"os"
|
2015-09-11 19:02:22 +00:00
|
|
|
"strings"
|
2015-09-06 00:06:05 +00:00
|
|
|
"testing"
|
2015-09-06 01:41:00 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/nomad"
|
2016-06-17 05:26:45 +00:00
|
|
|
sconfig "github.com/hashicorp/nomad/nomad/structs/config"
|
2015-09-06 00:06:05 +00:00
|
|
|
)
|
|
|
|
|
2016-11-09 00:13:09 +00:00
|
|
|
// getTestLogger returns a log func appropriate for passing to
|
|
|
|
// Config.normalize()
|
|
|
|
func getTestLogger(t testing.TB) func(string) {
|
|
|
|
return func(s string) {
|
|
|
|
t.Log(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-06 00:06:05 +00:00
|
|
|
func getPort() int {
|
2016-08-16 23:05:37 +00:00
|
|
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
l, err := net.ListenTCP("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer l.Close()
|
|
|
|
return l.Addr().(*net.TCPAddr).Port
|
2015-09-06 00:06:05 +00:00
|
|
|
}
|
|
|
|
|
2016-05-18 01:05:00 +00:00
|
|
|
func tmpDir(t testing.TB) string {
|
2015-09-06 00:06:05 +00:00
|
|
|
dir, err := ioutil.TempDir("", "nomad")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
return dir
|
|
|
|
}
|
|
|
|
|
2016-05-18 01:05:00 +00:00
|
|
|
func makeAgent(t testing.TB, cb func(*Config)) (string, *Agent) {
|
2015-09-06 00:06:05 +00:00
|
|
|
dir := tmpDir(t)
|
|
|
|
conf := DevConfig()
|
|
|
|
|
2015-09-06 01:41:00 +00:00
|
|
|
// Customize the server configuration
|
|
|
|
config := nomad.DefaultConfig()
|
|
|
|
conf.NomadConfig = config
|
|
|
|
|
2015-09-25 19:19:04 +00:00
|
|
|
// Bind and set ports
|
|
|
|
conf.BindAddr = "127.0.0.1"
|
|
|
|
conf.Ports = &Ports{
|
|
|
|
HTTP: getPort(),
|
|
|
|
RPC: getPort(),
|
|
|
|
Serf: getPort(),
|
2015-09-06 01:41:00 +00:00
|
|
|
}
|
2015-09-25 19:19:04 +00:00
|
|
|
conf.NodeName = fmt.Sprintf("Node %d", conf.Ports.RPC)
|
2016-06-17 05:26:45 +00:00
|
|
|
conf.Consul = sconfig.DefaultConsulConfig()
|
2016-10-11 01:04:39 +00:00
|
|
|
conf.Vault.Enabled = new(bool)
|
2015-09-06 01:41:00 +00:00
|
|
|
|
|
|
|
// Tighten the Serf timing
|
|
|
|
config.SerfConfig.MemberlistConfig.SuspicionMult = 2
|
|
|
|
config.SerfConfig.MemberlistConfig.RetransmitMult = 2
|
|
|
|
config.SerfConfig.MemberlistConfig.ProbeTimeout = 50 * time.Millisecond
|
|
|
|
config.SerfConfig.MemberlistConfig.ProbeInterval = 100 * time.Millisecond
|
|
|
|
config.SerfConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond
|
|
|
|
|
|
|
|
// Tighten the Raft timing
|
|
|
|
config.RaftConfig.LeaderLeaseTimeout = 20 * time.Millisecond
|
|
|
|
config.RaftConfig.HeartbeatTimeout = 40 * time.Millisecond
|
|
|
|
config.RaftConfig.ElectionTimeout = 40 * time.Millisecond
|
2015-09-07 17:46:41 +00:00
|
|
|
config.RaftConfig.StartAsLeader = true
|
2015-09-06 01:41:00 +00:00
|
|
|
config.RaftTimeout = 500 * time.Millisecond
|
|
|
|
|
2015-09-06 00:06:05 +00:00
|
|
|
if cb != nil {
|
|
|
|
cb(conf)
|
|
|
|
}
|
|
|
|
|
2016-11-09 00:13:09 +00:00
|
|
|
if ok := conf.normalize(getTestLogger(t), config.DevMode); !ok {
|
|
|
|
t.Fatalf("error normalizing config")
|
|
|
|
}
|
2015-09-06 00:06:05 +00:00
|
|
|
agent, err := NewAgent(conf, os.Stderr)
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(dir)
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
return dir, agent
|
|
|
|
}
|
2015-09-06 00:07:36 +00:00
|
|
|
|
|
|
|
func TestAgent_RPCPing(t *testing.T) {
|
|
|
|
dir, agent := makeAgent(t, nil)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
defer agent.Shutdown()
|
|
|
|
|
|
|
|
var out struct{}
|
|
|
|
if err := agent.RPC("Status.Ping", struct{}{}, &out); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
|
|
|
|
func TestAgent_ServerConfig(t *testing.T) {
|
|
|
|
conf := DefaultConfig()
|
|
|
|
a := &Agent{config: conf}
|
2016-11-09 00:13:09 +00:00
|
|
|
testlogger := getTestLogger(t)
|
2016-11-09 00:02:20 +00:00
|
|
|
dev := false
|
2015-09-11 19:02:22 +00:00
|
|
|
|
|
|
|
// Returns error on bad serf addr
|
|
|
|
conf.AdvertiseAddrs.Serf = "nope"
|
|
|
|
_, err := a.serverConfig()
|
2016-11-01 20:23:44 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Failed to parse Serf") {
|
2015-09-11 19:02:22 +00:00
|
|
|
t.Fatalf("expected serf address error, got: %#v", err)
|
|
|
|
}
|
|
|
|
conf.AdvertiseAddrs.Serf = "127.0.0.1:4000"
|
|
|
|
|
|
|
|
// Returns error on bad rpc addr
|
|
|
|
conf.AdvertiseAddrs.RPC = "nope"
|
|
|
|
_, err = a.serverConfig()
|
2016-11-01 20:23:44 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Failed to parse RPC") {
|
2015-09-11 19:02:22 +00:00
|
|
|
t.Fatalf("expected rpc address error, got: %#v", err)
|
|
|
|
}
|
|
|
|
conf.AdvertiseAddrs.RPC = "127.0.0.1:4001"
|
2016-05-25 01:33:24 +00:00
|
|
|
conf.AdvertiseAddrs.HTTP = "10.10.11.1:4005"
|
2015-09-11 19:02:22 +00:00
|
|
|
|
|
|
|
// Parses the advertise addrs correctly
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
out, err := a.serverConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
serfAddr := out.SerfConfig.MemberlistConfig.AdvertiseAddr
|
|
|
|
if serfAddr != "127.0.0.1" {
|
|
|
|
t.Fatalf("expect 127.0.0.1, got: %s", serfAddr)
|
|
|
|
}
|
|
|
|
serfPort := out.SerfConfig.MemberlistConfig.AdvertisePort
|
|
|
|
if serfPort != 4000 {
|
|
|
|
t.Fatalf("expected 4000, got: %d", serfPort)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
|
|
|
|
// Assert addresses weren't changed
|
|
|
|
if addr := conf.AdvertiseAddrs.RPC; addr != "127.0.0.1:4001" {
|
2015-09-11 19:02:22 +00:00
|
|
|
t.Fatalf("bad rpc advertise addr: %#v", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.AdvertiseAddrs.HTTP; addr != "10.10.11.1:4005" {
|
2016-05-25 01:33:24 +00:00
|
|
|
t.Fatalf("expect 10.11.11.1:4005, got: %v", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.RPC; addr != "0.0.0.0:4647" {
|
|
|
|
t.Fatalf("expect 0.0.0.0:4001, got: %v", addr)
|
2016-06-01 08:08:15 +00:00
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
|
|
|
|
// Sets up the ports properly
|
2016-11-09 00:02:20 +00:00
|
|
|
conf.Addresses.RPC = ""
|
|
|
|
conf.Addresses.Serf = ""
|
2015-09-11 19:02:22 +00:00
|
|
|
conf.Ports.RPC = 4003
|
|
|
|
conf.Ports.Serf = 4004
|
|
|
|
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if addr := out.RPCAddr.Port; addr != 4003 {
|
|
|
|
t.Fatalf("expect 4003, got: %d", out.RPCAddr.Port)
|
|
|
|
}
|
|
|
|
if port := out.SerfConfig.MemberlistConfig.BindPort; port != 4004 {
|
|
|
|
t.Fatalf("expect 4004, got: %d", port)
|
|
|
|
}
|
|
|
|
|
2016-06-01 08:08:15 +00:00
|
|
|
// Prefers advertise over bind addr
|
2015-09-11 19:02:22 +00:00
|
|
|
conf.BindAddr = "127.0.0.3"
|
2016-11-09 00:02:20 +00:00
|
|
|
conf.Addresses.HTTP = "127.0.0.2"
|
2015-09-11 19:02:22 +00:00
|
|
|
conf.Addresses.RPC = "127.0.0.2"
|
|
|
|
conf.Addresses.Serf = "127.0.0.2"
|
2016-11-01 01:08:49 +00:00
|
|
|
conf.AdvertiseAddrs.HTTP = "10.0.0.10"
|
2016-09-02 23:23:45 +00:00
|
|
|
conf.AdvertiseAddrs.RPC = ""
|
2016-09-11 18:02:11 +00:00
|
|
|
conf.AdvertiseAddrs.Serf = "10.0.0.12:4004"
|
2015-09-11 19:02:22 +00:00
|
|
|
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
out, err = a.serverConfig()
|
2016-11-09 00:02:20 +00:00
|
|
|
fmt.Println(conf.Addresses.RPC)
|
2015-09-11 19:02:22 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if addr := out.RPCAddr.IP.String(); addr != "127.0.0.2" {
|
|
|
|
t.Fatalf("expect 127.0.0.2, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if port := out.RPCAddr.Port; port != 4003 {
|
|
|
|
t.Fatalf("expect 4647, got: %d", port)
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.2" {
|
|
|
|
t.Fatalf("expect 127.0.0.2, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if port := out.SerfConfig.MemberlistConfig.BindPort; port != 4004 {
|
|
|
|
t.Fatalf("expect 4648, got: ^d", port)
|
2016-09-02 23:23:45 +00:00
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.HTTP; addr != "127.0.0.2:4646" {
|
2016-09-02 23:23:45 +00:00
|
|
|
t.Fatalf("expect 127.0.0.2:4646, got: %s", addr)
|
2016-05-25 01:33:24 +00:00
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.RPC; addr != "127.0.0.2:4003" {
|
2016-09-11 18:02:11 +00:00
|
|
|
t.Fatalf("expect 127.0.0.2:4003, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.Serf; addr != "127.0.0.2:4004" {
|
2016-09-11 18:02:11 +00:00
|
|
|
t.Fatalf("expect 10.0.0.12:4004, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.AdvertiseAddrs.HTTP; addr != "10.0.0.10:4646" {
|
|
|
|
t.Fatalf("expect 10.0.0.10:4646, got: %s", addr)
|
2016-09-11 18:40:52 +00:00
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.AdvertiseAddrs.RPC; addr != "127.0.0.3:4003" {
|
|
|
|
t.Fatalf("expect 127.0.0.3:4003, got: %s", addr)
|
2016-09-11 18:40:52 +00:00
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.AdvertiseAddrs.Serf; addr != "10.0.0.12:4004" {
|
|
|
|
t.Fatalf("expect 10.0.0.12:4004, got: %s", addr)
|
2016-09-11 18:02:11 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 13:47:06 +00:00
|
|
|
conf.Server.NodeGCThreshold = "42g"
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-10-29 13:47:06 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "unknown unit") {
|
|
|
|
t.Fatalf("expected unknown unit error, got: %#v", err)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
|
2015-10-29 13:47:06 +00:00
|
|
|
conf.Server.NodeGCThreshold = "10s"
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-10-29 13:47:06 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if threshold := out.NodeGCThreshold; threshold != time.Second*10 {
|
|
|
|
t.Fatalf("expect 10s, got: %s", threshold)
|
|
|
|
}
|
|
|
|
|
2016-03-04 23:44:12 +00:00
|
|
|
conf.Server.HeartbeatGrace = "42g"
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2016-03-04 23:44:12 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "unknown unit") {
|
|
|
|
t.Fatalf("expected unknown unit error, got: %#v", err)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
|
2016-03-04 23:44:12 +00:00
|
|
|
conf.Server.HeartbeatGrace = "37s"
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2016-03-04 23:44:12 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if threshold := out.HeartbeatGrace; threshold != time.Second*37 {
|
|
|
|
t.Fatalf("expect 37s, got: %s", threshold)
|
|
|
|
}
|
|
|
|
|
2015-09-11 19:02:22 +00:00
|
|
|
// Defaults to the global bind addr
|
|
|
|
conf.Addresses.RPC = ""
|
|
|
|
conf.Addresses.Serf = ""
|
2016-05-25 01:33:24 +00:00
|
|
|
conf.Addresses.HTTP = ""
|
2016-06-01 08:08:15 +00:00
|
|
|
conf.AdvertiseAddrs.RPC = ""
|
|
|
|
conf.AdvertiseAddrs.HTTP = ""
|
|
|
|
conf.AdvertiseAddrs.Serf = ""
|
|
|
|
conf.Ports.HTTP = 4646
|
|
|
|
conf.Ports.RPC = 4647
|
|
|
|
conf.Ports.Serf = 4648
|
2016-11-09 00:02:20 +00:00
|
|
|
if ok := conf.normalize(testlogger, dev); !ok {
|
|
|
|
t.Fatalf("failed to normalize config")
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if addr := out.RPCAddr.IP.String(); addr != "127.0.0.3" {
|
|
|
|
t.Fatalf("expect 127.0.0.3, got: %s", addr)
|
|
|
|
}
|
|
|
|
if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.3" {
|
|
|
|
t.Fatalf("expect 127.0.0.3, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.HTTP; addr != "127.0.0.3:4646" {
|
2016-05-25 01:33:24 +00:00
|
|
|
t.Fatalf("expect 127.0.0.3:4646, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.RPC; addr != "127.0.0.3:4647" {
|
2016-06-01 08:08:15 +00:00
|
|
|
t.Fatalf("expect 127.0.0.3:4647, got: %s", addr)
|
|
|
|
}
|
2016-11-09 00:02:20 +00:00
|
|
|
if addr := conf.Addresses.Serf; addr != "127.0.0.3:4648" {
|
2016-06-01 08:08:15 +00:00
|
|
|
t.Fatalf("expect 127.0.0.3:4648, got: %s", addr)
|
|
|
|
}
|
2015-09-22 21:25:43 +00:00
|
|
|
|
|
|
|
// Properly handles the bootstrap flags
|
|
|
|
conf.Server.BootstrapExpect = 1
|
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if !out.Bootstrap {
|
|
|
|
t.Fatalf("should have set bootstrap mode")
|
|
|
|
}
|
|
|
|
if out.BootstrapExpect != 0 {
|
|
|
|
t.Fatalf("boostrap expect should be 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
conf.Server.BootstrapExpect = 3
|
|
|
|
out, err = a.serverConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if out.Bootstrap {
|
|
|
|
t.Fatalf("bootstrap mode should be disabled")
|
|
|
|
}
|
|
|
|
if out.BootstrapExpect != 3 {
|
|
|
|
t.Fatalf("should have bootstrap-expect = 3")
|
|
|
|
}
|
2015-09-11 19:02:22 +00:00
|
|
|
}
|
2016-02-16 21:42:48 +00:00
|
|
|
|
|
|
|
func TestAgent_ClientConfig(t *testing.T) {
|
|
|
|
conf := DefaultConfig()
|
2016-11-09 00:35:11 +00:00
|
|
|
// enabled just to allow using localhost for all addresses
|
|
|
|
conf.DevMode = true
|
2016-02-16 21:42:48 +00:00
|
|
|
a := &Agent{config: conf}
|
|
|
|
conf.Client.Enabled = true
|
|
|
|
conf.Addresses.HTTP = "127.0.0.1"
|
|
|
|
conf.Ports.HTTP = 5678
|
|
|
|
|
2016-11-09 00:35:11 +00:00
|
|
|
if ok := conf.normalize(getTestLogger(t), conf.DevMode); !ok {
|
|
|
|
t.Fatalf("error normalizing config")
|
|
|
|
}
|
2016-02-16 21:42:48 +00:00
|
|
|
c, err := a.clientConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("got err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedHttpAddr := "127.0.0.1:5678"
|
|
|
|
if c.Node.HTTPAddr != expectedHttpAddr {
|
|
|
|
t.Fatalf("Expected http addr: %v, got: %v", expectedHttpAddr, c.Node.HTTPAddr)
|
|
|
|
}
|
2016-02-17 01:54:17 +00:00
|
|
|
|
|
|
|
conf = DefaultConfig()
|
2016-11-09 00:35:11 +00:00
|
|
|
conf.DevMode = true
|
2016-02-17 01:54:17 +00:00
|
|
|
a = &Agent{config: conf}
|
|
|
|
conf.Client.Enabled = true
|
|
|
|
conf.Addresses.HTTP = "127.0.0.1"
|
|
|
|
|
2016-11-09 00:35:11 +00:00
|
|
|
if ok := conf.normalize(getTestLogger(t), conf.DevMode); !ok {
|
|
|
|
t.Fatalf("error normalizing config")
|
|
|
|
}
|
2016-02-17 01:54:17 +00:00
|
|
|
c, err = a.clientConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("got err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedHttpAddr = "127.0.0.1:4646"
|
|
|
|
if c.Node.HTTPAddr != expectedHttpAddr {
|
|
|
|
t.Fatalf("Expected http addr: %v, got: %v", expectedHttpAddr, c.Node.HTTPAddr)
|
|
|
|
}
|
2016-02-16 21:42:48 +00:00
|
|
|
}
|