Skip to content

Commit 180d267

Browse files
authored
Merge pull request #9 from maxtek6/feature/tcp-list-namespaces
List Namespaces
2 parents f5f3d42 + 5ae4d26 commit 180d267

5 files changed

Lines changed: 105 additions & 27 deletions

File tree

client/client.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import (
55
"context"
66
"errors"
77
"fmt"
8-
"github.com/mailsac/dracula/client/serverpool"
9-
"github.com/mailsac/dracula/client/waitingmessage"
10-
"github.com/mailsac/dracula/protocol"
11-
"github.com/mailsac/dracula/server/rawmessage"
128
"io/ioutil"
139
"log"
1410
"math/rand"
@@ -19,6 +15,11 @@ import (
1915
"sync"
2016
"sync/atomic"
2117
"time"
18+
19+
"github.com/mailsac/dracula/client/serverpool"
20+
"github.com/mailsac/dracula/client/waitingmessage"
21+
"github.com/mailsac/dracula/protocol"
22+
"github.com/mailsac/dracula/server/rawmessage"
2223
)
2324

2425
var (
@@ -407,6 +408,26 @@ func (c *Client) CountServer() (int, error) {
407408
return int(output), err
408409
}
409410

411+
func (c *Client) ListNamespaces() ([]string, error) {
412+
var err error
413+
messageID := c.makeMessageID()
414+
wg := new(sync.WaitGroup)
415+
namespaces := ""
416+
cb := func(b []byte, e error) {
417+
defer wg.Done()
418+
if e != nil {
419+
err = e
420+
return
421+
}
422+
namespaces = string(b)
423+
}
424+
wg.Add(1)
425+
sendPacket := protocol.NewPacketFromParts(protocol.CmdTCPOnlyNamespaces, messageID, []byte{}, []byte{}, c.preSharedKey)
426+
c.sendOrCallbackErr(sendPacket, cb)
427+
wg.Wait()
428+
return strings.Split(namespaces, "\n"), err
429+
}
430+
410431
func (c *Client) Put(namespace, value string) error {
411432
messageID := c.makeMessageID()
412433
var wg sync.WaitGroup

client/client_test.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package client
22

33
import (
4-
"github.com/mailsac/dracula/protocol"
5-
"github.com/mailsac/dracula/server"
6-
"github.com/stretchr/testify/assert"
74
"math"
85
"sync"
96
"testing"
107
"time"
8+
9+
"github.com/mailsac/dracula/protocol"
10+
"github.com/mailsac/dracula/server"
11+
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestClient_Auth(t *testing.T) {
@@ -163,3 +164,33 @@ func TestClient_TcpKeyMatch(t *testing.T) {
163164
assert.ElementsMatch(t, []string{}, matched)
164165
})
165166
}
167+
168+
func TestClient_TcpListNamespaces(t *testing.T) {
169+
t.Run("returns a list of namespaces", func(t *testing.T) {
170+
secret := "asdf-!!?!|asdf"
171+
s := server.NewServer(60, secret)
172+
s.DebugEnable("9011")
173+
err := s.Listen(9011, 9011)
174+
if err != nil {
175+
t.Fatal(err)
176+
}
177+
defer s.Close()
178+
179+
cl := NewClient(Config{RemoteUDPIPPortList: "127.0.0.1:9011", RemoteTCPIPPortList: "127.0.0.1:9011", Timeout: time.Second * 2, PreSharedKey: secret})
180+
assert.NoError(t, cl.Listen(9012))
181+
defer cl.Close()
182+
183+
insertValues := map[string]string{
184+
"namespace0": "key0",
185+
"namespace1": "key1",
186+
}
187+
188+
for namespace, value := range insertValues {
189+
assert.NoError(t, cl.Put(namespace, value))
190+
}
191+
192+
namespaces, err := cl.ListNamespaces()
193+
assert.Len(t, namespaces, 2)
194+
assert.NoError(t, err) // out of order
195+
})
196+
}

cmd/cli/main.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package main
33
import (
44
"flag"
55
"fmt"
6-
"github.com/mailsac/dracula/client"
76
"os"
87
"time"
8+
9+
"github.com/mailsac/dracula/client"
910
)
1011

1112
var (
@@ -15,6 +16,7 @@ var (
1516
count = flag.Bool("count", false, "Mode: Count items at entry key")
1617
put = flag.Bool("put", false, "Mode: Put item at entry key")
1718
cmdKeys = flag.Bool("keys", false, "Mode: list keys matching this pattern (TCP)")
19+
namespaces = flag.Bool("namespaces", false, "Mode: list namespaces")
1820
secret = flag.String("s", "", "Optional pre-shared auth secret if not using env var DRACULA_SECRET")
1921
localPort = flag.Int("p", 3510, "Local client port to receive responses on")
2022
timeoutSecs = flag.Int64("t", 6, "Request timeout in seconds")
@@ -121,6 +123,20 @@ func main() {
121123

122124
os.Exit(0)
123125
}
126+
if *namespaces {
127+
namespaceList, err := c.ListNamespaces()
128+
if err != nil {
129+
fmt.Println(err)
130+
os.Exit(1)
131+
}
132+
if len(namespaceList) > 0 {
133+
for index, namespace := range namespaceList {
134+
fmt.Printf("%d) %s\n", index+1, namespace)
135+
}
136+
} else {
137+
fmt.Println("(no namespaces)")
138+
}
139+
}
124140

125141
fmt.Println("no command matched")
126142
os.Exit(1)

protocol/protocol.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,39 @@ import (
44
"encoding/binary"
55
"errors"
66
"fmt"
7-
"github.com/OneOfOne/xxhash"
87
"log"
98
"math"
109
"net"
1110
"strings"
11+
12+
"github.com/OneOfOne/xxhash"
1213
)
1314

1415
const (
15-
PacketSize = 1500
16-
NamespaceSize = 64
17-
DataValueSize = 1419
16+
PacketSize int = 1500
17+
NamespaceSize int = 64
18+
DataValueSize int = 1419
1819

1920
CmdCount byte = 'C'
2021
CmdPut byte = 'P'
2122
CmdPutReplicate byte = 'R'
2223
CmdCountNamespace byte = 'N'
2324
CmdCountServer byte = 'S'
2425

25-
CmdTCPOnlyKeys byte = 'K'
26-
CmdTCPOnlyValues byte = 'V'
27-
CmdTCPOnlyStore byte = 'T'
28-
CmdTCPOnlyRetrieve byte = 'I'
26+
CmdTCPOnlyKeys byte = 'K'
27+
CmdTCPOnlyValues byte = 'V'
28+
CmdTCPOnlyStore byte = 'T'
29+
CmdTCPOnlyRetrieve byte = 'I'
30+
CmdTCPOnlyNamespaces byte = 'L'
2931

3032
// ResError is a Cmd
3133
ResError byte = 'E'
3234

3335
space byte = ' '
34-
spaceIndex1 = 1
35-
spaceIndex2 = 10
36-
spaceIndex3 = 15
37-
spaceIndex4 = 80
36+
spaceIndex1 int = 1
37+
spaceIndex2 int = 10
38+
spaceIndex3 int = 15
39+
spaceIndex4 int = 80
3840
)
3941

4042
var (
@@ -56,7 +58,7 @@ func IsRequestCmd(c byte) bool {
5658
}
5759

5860
func IsTcpOnlyCmd(c byte) bool {
59-
return c == CmdTCPOnlyKeys || c == CmdTCPOnlyRetrieve || c == CmdTCPOnlyValues || c == CmdTCPOnlyStore
61+
return c == CmdTCPOnlyKeys || c == CmdTCPOnlyRetrieve || c == CmdTCPOnlyValues || c == CmdTCPOnlyStore || c == CmdTCPOnlyNamespaces
6062
}
6163

6264
// IsResponseCmd indicates if the client should accept this as a command
@@ -106,7 +108,8 @@ func (p *Packet) DataValueString() string {
106108
}
107109

108110
// ParsePacket parses a packet like:
109-
// [Command char][space][xxhash of pre shared key + id + ns + data][space][Message ID uint32][space][Namespace 64 bytes][space][data remaining bytes]
111+
//
112+
// [Command char][space][xxhash of pre shared key + id + ns + data][space][Message ID uint32][space][Namespace 64 bytes][space][data remaining bytes]
110113
//
111114
// The MTU of 1500 is the maximum allowed packet size. That means the data key can only be 1419
112115
// bytes max.
@@ -125,7 +128,7 @@ func ParsePacket(buf []byte) (*Packet, error) {
125128
idBytes := buf[spaceIndex2+1 : spaceIndex3]
126129
nsBytes := buf[spaceIndex3+1 : spaceIndex4]
127130
// allows shorter packet to be turned into 1500 byte total packet
128-
endAt := int(math.Min(float64(len(buf)), PacketSize))
131+
endAt := int(math.Min(float64(len(buf)), float64(PacketSize)))
129132
messageIData := buf[spaceIndex4+1 : endAt]
130133
rightSizeData := *PadRight(&messageIData, DataValueSize)
131134
p := Packet{
@@ -173,7 +176,7 @@ func ParsePacket(buf []byte) (*Packet, error) {
173176
}
174177

175178
// bytes formats the packet for transport. The first 8 bytes are a header.
176-
//// The last byte should be a line break. The data is a UTF-8 string.
179+
// // The last byte should be a line break. The data is a UTF-8 string.
177180
func (p *Packet) bytes() []byte {
178181
//fmt.Println("Bytes() message id", p.MessageID, "|")
179182
//fmt.Println("Bytes() Namespace", p.Namespace, "|", len(p.Namespace))

server/server.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package server
22

33
import (
44
"errors"
5-
"github.com/mailsac/dracula/protocol"
6-
"github.com/mailsac/dracula/server/rawmessage"
7-
"github.com/mailsac/dracula/store"
85
"io/ioutil"
96
"log"
107
"math"
@@ -14,6 +11,10 @@ import (
1411
"runtime"
1512
"strconv"
1613
"strings"
14+
15+
"github.com/mailsac/dracula/protocol"
16+
"github.com/mailsac/dracula/server/rawmessage"
17+
"github.com/mailsac/dracula/store"
1718
)
1819

1920
const MinimumExpirySecs = 2
@@ -298,6 +299,12 @@ func (s *Server) worker(messages <-chan *rawmessage.RawMessage) {
298299
resPacket = protocol.NewPacketFromParts(protocol.CmdTCPOnlyKeys, packet.MessageIDBytes, packet.Namespace, []byte(strings.Join(matchedKeys, "\n")), s.preSharedKey)
299300
respond()
300301
break
302+
case protocol.CmdTCPOnlyNamespaces:
303+
namespaces := s.store.Namespaces()
304+
s.log.Println("Namespaces", packet.NamespaceString(), packet.DataValueString(), namespaces)
305+
resPacket = protocol.NewPacketFromParts(protocol.CmdTCPOnlyNamespaces, packet.MessageIDBytes, packet.Namespace, []byte(strings.Join(namespaces, "\n")), s.preSharedKey)
306+
respond()
307+
break
301308
default:
302309
resPacket = protocol.NewPacketFromParts(protocol.ResError, packet.MessageIDBytes, packet.Namespace, []byte("unknown_command_"+string(packet.Command)), s.preSharedKey)
303310
respond()

0 commit comments

Comments
 (0)