├── .gitignore ├── README.md ├── TODO ├── consensus_failure ├── pydebug ├── __init__.py └── debug.py ├── requirements.txt └── src ├── cluster ├── cluster.go ├── cluster_datacenter_join_test.go ├── cluster_integration_test.go ├── cluster_join_test.go ├── cluster_move_node_test.go ├── cluster_query_test.go ├── cluster_remove_node_test.go ├── cluster_streaming_test.go ├── cluster_test.go ├── connection.go ├── connection_test.go ├── connectionpool_test.go ├── message.go ├── message_connect.go ├── message_query.go ├── message_stream.go ├── message_test.go ├── node.go ├── node_mock.go ├── node_test.go ├── server.go ├── server_response_test.go ├── server_test.go ├── store_mock.go └── testing.go ├── clusterproto └── clusterproto.go ├── consensus ├── algorithms.go ├── algorithms_test.go ├── consensus.go ├── consensus_test.go ├── errors.go ├── instance.go ├── instance_containers.go ├── instance_test.go ├── integration_test.go ├── manager.go ├── manager_accept.go ├── manager_accept_test.go ├── manager_commit.go ├── manager_commit_test.go ├── manager_depedencies.go ├── manager_dependencies_test.go ├── manager_execute.go ├── manager_execute_test.go ├── manager_leader_test.go ├── manager_preaccept.go ├── manager_preaccept_test.go ├── manager_prepare.go ├── manager_prepare_test.go ├── manager_test.go ├── message.go ├── message_test.go ├── mock_cluster_test.go ├── testing_checks.go ├── testing_mocks.go └── testing_setup.go ├── disk └── disk.go ├── kvstore ├── cmd_del.go ├── cmd_del_test.go ├── cmd_get.go ├── cmd_get_test.go ├── cmd_set.go ├── cmd_set_test.go ├── store.go ├── store_test.go ├── testing.go ├── val_bool.go ├── val_bool_test.go ├── val_string.go ├── val_string_test.go ├── val_tombstone.go ├── val_tombstone_test.go └── values.go ├── main.go ├── message ├── errors.go ├── io.go └── message.go ├── node └── node.go ├── partitioner ├── md5.go ├── md5_test.go ├── partitioner.go └── partitioner_test.go ├── redis ├── cmd_del.go ├── cmd_del_test.go ├── cmd_get.go ├── cmd_get_test.go ├── cmd_set.go ├── cmd_set_test.go ├── store.go ├── store_test.go ├── testing.go ├── val_bool.go ├── val_bool_test.go ├── val_string.go ├── val_string_test.go ├── val_tombstone.go ├── val_tombstone_test.go └── values.go ├── serializer └── serializer.go ├── server └── server.go ├── store ├── instruction.go ├── instruction_test.go └── store.go ├── testing_helpers └── helpers.go ├── topology ├── datacenter.go ├── datacenter_test.go ├── mocks.go ├── node.go ├── ring.go ├── ring_test.go ├── topology.go └── topology_test.go └── types ├── b16.go ├── b16_test.go ├── types_test.go ├── uuid.go └── uuid_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.test 6 | out/ 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Debug data 13 | debug/ 14 | 15 | # Dependencies 16 | code.google.*/ 17 | github.*/ 18 | launchpad.*/ 19 | 20 | # Architecture specific extensions/prefixes 21 | *.[568vq] 22 | [568vq].out 23 | 24 | *.cgo1.go 25 | *.cgo2.c 26 | _cgo_defun.c 27 | _cgo_gotypes.go 28 | _cgo_export.* 29 | 30 | _testmain.go 31 | 32 | *.exe 33 | 34 | # IDE 35 | *.iml 36 | .idea/ 37 | .project 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kickboxer 2 | ========= 3 | 4 | A clustered, masterless redis clone. Built in Go 5 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | logging: 2 | https://github.com/op/go-logging 3 | 4 | statsd: 5 | https://github.com/cactus/go-statsd-client 6 | -------------------------------------------------------------------------------- /consensus_failure: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | ipython -i -- pydebug/debug.py $@ -------------------------------------------------------------------------------- /pydebug/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'bdeggleston' 2 | -------------------------------------------------------------------------------- /pydebug/debug.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pprint import pformat 3 | import re 4 | import os 5 | from uuid import UUID 6 | 7 | import argparse 8 | from tarjan import tarjan 9 | 10 | parser = argparse.ArgumentParser(description='debug failed consensus test') 11 | parser.add_argument('path', help='path to the debug info') 12 | parser.add_argument('key', help='key to examine') 13 | parser.add_argument('--replica', '-r', metavar='replica', type=int, default=0, help='replica to work with') 14 | parser.add_argument('--instance', '-i', metavar='instance', default=None, help='initial instance') 15 | argv = parser.parse_args() 16 | 17 | REPLICA = argv.replica 18 | PATH = re.sub(r'\/+$', '', argv.path) 19 | KEY = argv.key 20 | 21 | print "" 22 | print "" 23 | 24 | if not os.path.isdir(PATH): 25 | print "invalid path", PATH 26 | 27 | if not any([fname.startswith('{}.'.format(KEY)) for fname in os.listdir(PATH)]): 28 | print "invalid key", KEY 29 | 30 | PREACCEPTED = 1 31 | ACCEPTED = 2 32 | COMMITTED = 3 33 | EXECUTED = 4 34 | instances = {} 35 | execution_order = [] 36 | 37 | 38 | class Instance(object): 39 | 40 | def __init__(self, data): 41 | self._data = data 42 | self.iid = data['InstanceID'] 43 | self.leader_id = data['LeaderID'] 44 | self.successors = data['Successors'] 45 | self.command = data['Command'] 46 | self.status = data['Status'] 47 | self.seq = data['Sequence'] 48 | self._deps = set(data['Dependencies']) 49 | self.strongly_connected = set(data.get('StronglyConnected')) 50 | self._in_deps = set() 51 | 52 | self.deps_log = data.get('DepsLog') 53 | 54 | def __repr__(self): 55 | return pformat(self._data) 56 | 57 | def _add_in_dep(self, iid): 58 | self._in_deps.add(iid) 59 | 60 | @classmethod 61 | def _add(cls, data): 62 | instance = cls(data) 63 | instances[instance.iid] = instance 64 | return instance 65 | 66 | @property 67 | def deps(self): 68 | return [instances.get(d) for d in self._deps] 69 | 70 | @property 71 | def in_deps(self): 72 | return [instances.get(d) for d in self._in_deps] 73 | 74 | def depends_on(self, *iids): 75 | return [iid in self._deps for iid in iids] 76 | 77 | def is_dependency_of(self, *iids): 78 | return [iid in self._in_deps for iid in iids] 79 | 80 | @property 81 | def is_strongly_connected(self): 82 | return len(self.strongly_connected) > 0 83 | 84 | 85 | with open('{}/{}:{}.instances.json'.format(PATH, KEY, REPLICA)) as f: 86 | for data in json.load(f): 87 | Instance._add(data) 88 | 89 | for instance in instances.values(): 90 | for dep in instance._deps: 91 | dep_instance = instances.get(dep) 92 | if dep_instance is not None: 93 | dep_instance._add_in_dep(instance.iid) 94 | 95 | with open('{}/{}:{}.execution.json'.format(PATH, KEY, REPLICA)) as f: 96 | for data in json.load(f): 97 | execution_order.append(data['InstanceID']) 98 | 99 | last_executed = instances.get(execution_order[-1]) 100 | 101 | # work out the expected execution order 102 | dep_graph = {} 103 | for instance in instances.values(): 104 | dep_graph[instance.iid] = instance._deps 105 | 106 | tsorted = tarjan(dep_graph) 107 | 108 | 109 | def _component_cmp(x, y): 110 | x = instances[x] 111 | y = instances[y] 112 | xID = UUID(x.iid) 113 | yID = UUID(y.iid) 114 | if x.seq != y.seq: 115 | return int(x.seq - y.seq) 116 | elif xID.time != yID.time: 117 | return int(xID.time - yID.time) 118 | else: 119 | return -1 if xID.bytes < yID.bytes else 1 120 | 121 | strong_map = {} 122 | for component in tsorted: 123 | if len(component) > 1: 124 | cset = set(component) 125 | for iid in cset: 126 | strong_map[iid] = cset 127 | 128 | 129 | expected_execution_order = sum([sorted(c, cmp=_component_cmp) for c in tsorted], []) 130 | 131 | minlen = min(len(execution_order), len(expected_execution_order)) 132 | 133 | def check_consistency(history=5): 134 | for i, (actual, expected) in enumerate(zip(execution_order[:minlen], expected_execution_order[:minlen])): 135 | if actual != expected: 136 | print "execution inconsistency at", i 137 | print "" 138 | start = max(0, i-history) 139 | end = i + history 140 | print '{:<36} {:<36}'.format('actual', 'expected') 141 | for a, e in zip(execution_order[start:end], expected_execution_order[start:end]): 142 | print a, e, '<-' if a != e else '' 143 | 144 | break 145 | 146 | check_consistency() 147 | 148 | print "" 149 | print "" 150 | 151 | def check_strong_connections(): 152 | for iid, component in strong_map.items(): 153 | instance = instances[iid] 154 | if instance.strongly_connected != component: 155 | if instance.status >= COMMITTED: 156 | print "strongly connected inconsistency for", iid 157 | print " expected:", component 158 | print " got:", instances[iid].strongly_connected 159 | print "" 160 | 161 | check_strong_connections() 162 | 163 | instance = instances.get(argv.instance) 164 | if instance: 165 | print str(instance) 166 | 167 | i = instances -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements for python debug 2 | argparse==1.2.1 3 | tarjan==0.1.2 4 | graphviz==0.2.2 5 | pygraphml==1.0 -------------------------------------------------------------------------------- /src/cluster/cluster_datacenter_join_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests around datacenters connecting to each other 3 | */ 4 | package cluster 5 | 6 | // TODO: this 7 | 8 | /** 9 | to test: 10 | 11 | empty dc join 12 | 2 way data reconciliation on join 13 | network partition recovery 14 | */ 15 | -------------------------------------------------------------------------------- /src/cluster/cluster_integration_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | // TODO: this 4 | /* 5 | to test: 6 | 7 | node joining 8 | token changing 9 | node leaving 10 | inconsistent views of ring between nodes` 11 | */ 12 | -------------------------------------------------------------------------------- /src/cluster/cluster_join_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | /** 3 | * Tests around nodes joining a cluster 4 | */ 5 | 6 | import ( 7 | "launchpad.net/gocheck" 8 | ) 9 | 10 | type JoinStreamTest struct {} 11 | 12 | var _ = gocheck.Suite(&JoinStreamTest{}) 13 | 14 | // TODO: this 15 | // tests that a node joining the cluster identifies 16 | // the correct node to stream data from, and sends 17 | // it a message 18 | func (t *JoinStreamTest) TestNodeStreamsFromCorrectNode(c *gocheck.C) { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/cluster/cluster_move_node_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | /** 3 | * Tests around changing node tokens 4 | */ 5 | 6 | // TODO: this 7 | -------------------------------------------------------------------------------- /src/cluster/cluster_remove_node_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | // TODO: this 3 | 4 | -------------------------------------------------------------------------------- /src/cluster/connection.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | 10 | // encapsulates a remote connection 11 | type Connection struct { 12 | socket net.Conn 13 | timeout int64 14 | isClosed bool 15 | completedHandshake bool 16 | } 17 | 18 | // connects and returns a new connection 19 | // timeout is the number of milliseconds until the connection 20 | // attempt is aborted 21 | func Connect(addr string, timeout int64) (*Connection, error) { 22 | conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout) * time.Millisecond) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Connection{socket:conn, timeout:timeout}, nil 27 | } 28 | 29 | // this implements the io.Reader interface 30 | func (c *Connection) Read(b []byte) (int, error) { 31 | size, err := c.socket.Read(b) 32 | if err != nil { 33 | c.isClosed = true 34 | } 35 | return size, err 36 | } 37 | 38 | // this implements the io.Writer interface 39 | func (c *Connection) Write(b []byte) (int, error) { 40 | size, err := c.socket.Write(b) 41 | if err != nil { 42 | c.isClosed = true 43 | } 44 | return size, err 45 | } 46 | 47 | // closes the connection 48 | func (c *Connection) Close() { 49 | c.socket.Close() 50 | c.isClosed = true 51 | } 52 | 53 | func (c *Connection) Closed() bool { 54 | return c.isClosed 55 | } 56 | 57 | func (c* Connection) HandshakeCompleted() bool { return c.completedHandshake } 58 | func (c* Connection) SetHandshakeCompleted() { c.completedHandshake = true } 59 | 60 | // linked list for connection pool 61 | type connHolder struct { 62 | conn *Connection 63 | next *connHolder 64 | } 65 | 66 | // pools connections to a single host 67 | type ConnectionPool struct { 68 | sync.RWMutex 69 | 70 | addr string 71 | size uint 72 | timeout int64 73 | maxConn uint 74 | 75 | pool *connHolder 76 | tail *connHolder 77 | } 78 | 79 | 80 | // creates a connection pool for the given address and max size 81 | // if the max size is 0, 10 is used, if the given timeout is 0 82 | // or less, 10 seconds is used 83 | func NewConnectionPool(addr string, maxConn uint, timeout int64) *ConnectionPool { 84 | if maxConn == 0 { 85 | maxConn = 10 86 | } 87 | 88 | // default connection timeout is 10 seconds 89 | if timeout <= 0 { 90 | timeout = 10000 91 | } 92 | return &ConnectionPool{addr:addr, maxConn:maxConn, timeout:timeout} 93 | } 94 | 95 | // returns a connection to the pool 96 | func (cp *ConnectionPool) Put(conn *Connection) { 97 | cp.Lock() 98 | defer cp.Unlock() 99 | 100 | if conn.isClosed { 101 | return 102 | } else if cp.size < cp.maxConn { 103 | holder := &connHolder{conn:conn} 104 | if cp.pool == nil { 105 | cp.pool = holder 106 | cp.tail = holder 107 | } else { 108 | cp.tail.next = holder 109 | cp.tail = holder 110 | } 111 | cp.size++ 112 | } else { 113 | conn.Close() 114 | } 115 | } 116 | 117 | // gets a connection from the pool, creating one if the 118 | // pool is empty 119 | func (cp *ConnectionPool) Get() (conn *Connection, err error) { 120 | if cp.pool == nil { 121 | return Connect(cp.addr, 1) 122 | } else { 123 | cp.Lock() 124 | defer cp.Unlock() 125 | // remove a connection from the pool 126 | conn = cp.pool.conn 127 | cp.pool = cp.pool.next 128 | cp.size-- 129 | 130 | // kill the tail if the pool is empty 131 | if cp.pool == nil { 132 | cp.tail = nil 133 | } 134 | } 135 | return 136 | } 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/cluster/connection_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | // test connecting to an address that's not listening 9 | func TestConnectionFailure(t *testing.T) { 10 | addr := "127.0.0.1:9999" 11 | 12 | conn, err := Connect(addr, 10) 13 | 14 | if conn != nil { 15 | t.Error("expected conn to be nil") 16 | } 17 | 18 | netErr, ok := err.(net.Error) 19 | if !ok { 20 | t.Errorf("expected error of type net.Error, got %T", err) 21 | } 22 | 23 | if netErr.Timeout() { 24 | t.Errorf("expected non timeout error") 25 | } 26 | } 27 | 28 | // test that read timeouts kill the connection 29 | func TestReadError(t *testing.T) { 30 | conn := &Connection{socket:&timeoutConn{}} 31 | bytes := make([]byte, 5) 32 | 33 | size, err := conn.Read(bytes) 34 | if size != 0 { 35 | t.Errorf("expected size of 0") 36 | } 37 | 38 | if err == nil { 39 | t.Fatal("error not returned") 40 | } 41 | 42 | netErr, ok := err.(net.Error) 43 | if !ok { 44 | t.Errorf("expected error of type net.Error, got %T", err) 45 | } 46 | 47 | if !netErr.Timeout() { 48 | t.Errorf("expected timeout error") 49 | } 50 | 51 | if !conn.Closed() { 52 | t.Errorf("expected connection to be closed") 53 | } 54 | } 55 | 56 | // test that write timeouts kill the connection 57 | func TestWriteError(t *testing.T) { 58 | conn := &Connection{socket:&timeoutConn{}} 59 | bytes := make([]byte, 5) 60 | 61 | size, err := conn.Write(bytes) 62 | if size != 0 { 63 | t.Errorf("expected size of 0") 64 | } 65 | 66 | if err == nil { 67 | t.Fatal("error not returned") 68 | } 69 | 70 | netErr, ok := err.(net.Error) 71 | if !ok { 72 | t.Errorf("expected error of type net.Error, got %T", err) 73 | } 74 | 75 | if !netErr.Timeout() { 76 | t.Errorf("expected timeout error") 77 | } 78 | 79 | if !conn.Closed() { 80 | t.Errorf("expected connection to be closed") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/cluster/connectionpool_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | import ( 8 | "launchpad.net/gocheck" 9 | ) 10 | 11 | type ConnectionPoolTest struct {} 12 | 13 | var _ = gocheck.Suite(&ConnectionPoolTest{}) 14 | 15 | func (t *ConnectionPoolTest) TestInitialization(c *gocheck.C) { 16 | addr := "127.0.0.1:9999" 17 | pool := NewConnectionPool(addr, 30, 10000) 18 | 19 | c.Check(pool.size, gocheck.Equals, uint(0)) 20 | c.Check(pool.addr, gocheck.Equals, addr) 21 | c.Check(pool.timeout, gocheck.Equals, int64(10000)) 22 | c.Check(pool.maxConn, gocheck.Equals, uint(30)) 23 | c.Check(pool.pool, gocheck.IsNil) 24 | c.Check(pool.tail, gocheck.IsNil) 25 | } 26 | 27 | // tests that open connections are returned to 28 | // the connection pool 29 | func (t *ConnectionPoolTest) TestOpenConnectionReturn(c *gocheck.C) { 30 | pool := NewConnectionPool("127.0.0.1:9999", 30, 10000) 31 | conn := &Connection{socket:&dumbConn{}} 32 | 33 | pool.Put(conn) 34 | 35 | c.Check(pool.size, gocheck.Equals, uint(1)) 36 | c.Check(pool.pool, gocheck.NotNil) 37 | c.Check(pool.tail, gocheck.NotNil) 38 | } 39 | 40 | // test that closed connections are not returned 41 | // to the connection pool 42 | func (t *ConnectionPoolTest) TestClosedConnectionReturn(c *gocheck.C) { 43 | pool := NewConnectionPool("127.0.0.1:9999", 30, 10000) 44 | conn := &Connection{socket:&dumbConn{}, isClosed:true} 45 | 46 | pool.Put(conn) 47 | 48 | c.Check(pool.size, gocheck.Equals, uint(0)) 49 | c.Check(pool.pool, gocheck.IsNil) 50 | c.Check(pool.tail, gocheck.IsNil) 51 | } 52 | 53 | // test that connections are closed if the 54 | // pool is full 55 | func (t *ConnectionPoolTest) TestFullConnectionReturn(c *gocheck.C) { 56 | pool := NewConnectionPool("127.0.0.1:9999", 10, 10000) 57 | for i:=0; i<10; i++ { 58 | pool.Put(&Connection{socket:&dumbConn{}}) 59 | 60 | } 61 | c.Check(pool.size, gocheck.Equals, uint(10)) 62 | 63 | conn := &Connection{socket:&dumbConn{}} 64 | pool.Put(conn) 65 | c.Check(pool.size, gocheck.Equals, uint(10)) 66 | c.Check(conn.isClosed, gocheck.Equals, true) 67 | } 68 | 69 | // test that open connections are returned 70 | func (t *ConnectionPoolTest) TestGetConn(c *gocheck.C) { 71 | pool := NewConnectionPool("127.0.0.1:9999", 30, 10000) 72 | conn := &Connection{socket:&dumbConn{}} 73 | 74 | pool.Put(conn) 75 | 76 | c.Check(pool.size, gocheck.Equals, uint(1)) 77 | 78 | newConn, err := pool.Get() 79 | 80 | c.Assert(err, gocheck.IsNil) 81 | c.Check(pool.size, gocheck.Equals, uint(0)) 82 | c.Check(conn, gocheck.Equals, newConn) 83 | } 84 | 85 | // test that connections are opened if the 86 | // pool is empty 87 | func (t *ConnectionPoolTest) TestGetOpensConnection(c *gocheck.C) { 88 | addr := "127.0.0.1:9999" 89 | ln, _ := unresponsiveListener(addr) 90 | defer ln.Close() 91 | 92 | pool := NewConnectionPool("127.0.0.1:9999", 10, 10000) 93 | c.Assert(pool.pool, gocheck.IsNil) 94 | conn, err := pool.Get() 95 | defer conn.Close() 96 | 97 | c.Assert(err, gocheck.IsNil) 98 | c.Assert(conn, gocheck.NotNil) 99 | 100 | c.Assert(conn.Closed(), gocheck.Equals, false) 101 | 102 | c.Assert(conn.socket, gocheck.FitsTypeOf, &net.TCPConn{}) 103 | } 104 | 105 | // tests that pool counter is incremented and 106 | // decremented properly 107 | func (t *ConnectionPoolTest) TestPoolSize(c *gocheck.C) { 108 | pool := NewConnectionPool("127.0.0.1:9999", 30, 10000) 109 | 110 | conn := &Connection{socket:&dumbConn{}} 111 | pool.Put(conn) 112 | c.Check(pool.size, gocheck.Equals, uint(1)) 113 | 114 | conn = &Connection{socket:&dumbConn{}} 115 | pool.Put(conn) 116 | c.Check(pool.size, gocheck.Equals, uint(2)) 117 | 118 | pool.Get() 119 | c.Check(pool.size, gocheck.Equals, uint(1)) 120 | } 121 | 122 | // tests that the tail member is tracked properly 123 | func (t *ConnectionPoolTest) TestTailLogic(c *gocheck.C) { 124 | pool := NewConnectionPool("127.0.0.1:9999", 30, 10000) 125 | 126 | conn := &Connection{socket:&dumbConn{}} 127 | pool.Put(conn) 128 | c.Assert(pool.tail.conn, gocheck.Equals, conn) 129 | 130 | conn = &Connection{socket:&dumbConn{}} 131 | pool.Put(conn) 132 | c.Assert(pool.tail.conn, gocheck.Equals, conn) 133 | 134 | // empty the pool 135 | pool.Get() 136 | pool.Get() 137 | 138 | c.Check(pool.size, gocheck.Equals, uint(0)) 139 | c.Assert(pool.tail, gocheck.IsNil) 140 | } 141 | -------------------------------------------------------------------------------- /src/cluster/message.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | type MessageError struct { 4 | reason string 5 | } 6 | 7 | func (e *MessageError) Error() string { 8 | return e.reason 9 | } 10 | 11 | type MessageEncodingError struct { 12 | MessageError 13 | } 14 | 15 | func NewMessageEncodingError(reason string) *MessageEncodingError { 16 | return &MessageEncodingError{MessageError{reason}} 17 | } 18 | 19 | // TODO: add a checksum to the head of the message 20 | // TODO: gzip message contents 21 | 22 | -------------------------------------------------------------------------------- /src/cluster/message_query.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "message" 11 | "serializer" 12 | ) 13 | 14 | const ( 15 | READ_REQUEST = uint32(301) 16 | WRITE_REQUEST = uint32(302) 17 | QUERY_RESPONSE = uint32(303) 18 | ) 19 | 20 | // ----------- query execution ----------- 21 | 22 | type ReadRequest struct { 23 | Cmd string 24 | Key string 25 | Args []string 26 | } 27 | 28 | var _ = message.Message(&ReadRequest{}) 29 | 30 | func (m *ReadRequest) Serialize(buf *bufio.Writer) error { 31 | if err := serializer.WriteFieldBytes(buf, []byte(m.Cmd)); err != nil { return err } 32 | if err := serializer.WriteFieldBytes(buf, []byte(m.Key)); err != nil { return err } 33 | numArgs := uint32(len(m.Args)) 34 | if err := binary.Write(buf, binary.LittleEndian, &numArgs); err != nil { return err } 35 | for i:=0;i lo { 50 | maxLen = li 51 | } else { 52 | maxLen = lo 53 | } 54 | n := make(InstanceIDSet, maxLen) 55 | for k := range i { 56 | if _, exists := o[k]; exists { 57 | n[k] = true 58 | } 59 | } 60 | 61 | return n 62 | } 63 | 64 | 65 | func (i InstanceIDSet) Add(ids ...InstanceID) { 66 | for _, id := range ids { 67 | i[id] = true 68 | } 69 | } 70 | 71 | func (i InstanceIDSet) Remove(ids ...InstanceID) { 72 | for _, id := range ids { 73 | delete(i, id) 74 | } 75 | } 76 | 77 | func (i InstanceIDSet) Combine(idSets ...InstanceIDSet) { 78 | for _, ids := range idSets { 79 | for id := range ids { 80 | i[id] = true 81 | } 82 | } 83 | } 84 | 85 | // returns a new set with all of the keys in i, that aren't in o 86 | func (i InstanceIDSet) Difference(o InstanceIDSet) InstanceIDSet { 87 | s := NewInstanceIDSet([]InstanceID{}) 88 | for key := range i { 89 | if !o.Contains(key) { 90 | s.Add(key) 91 | } 92 | } 93 | return s 94 | } 95 | 96 | // remove all the keys in o, from i, returns removed keys 97 | func (i InstanceIDSet) Subtract(o InstanceIDSet) { 98 | for key := range o { 99 | delete(i, key) 100 | } 101 | } 102 | 103 | // remove all keys from i 104 | func (i InstanceIDSet) Clear() { 105 | for key := range i { 106 | delete(i, key) 107 | } 108 | } 109 | 110 | func (i InstanceIDSet) Copy() InstanceIDSet { 111 | c := NewSizedInstanceIDSet(i.Size()) 112 | for k := range i { 113 | c[k] = true 114 | } 115 | return c 116 | } 117 | 118 | func (i InstanceIDSet) Contains(id InstanceID) bool { 119 | _, exists := i[id] 120 | return exists 121 | } 122 | 123 | func (i InstanceIDSet) Size() int { 124 | return len(i) 125 | } 126 | 127 | func (i InstanceIDSet) List() []InstanceID { 128 | l := make([]InstanceID, 0, len(i)) 129 | for k := range i { 130 | l = append(l, k) 131 | } 132 | return l 133 | } 134 | 135 | func (i InstanceIDSet) String() string { 136 | s := "{" 137 | n := 0 138 | for k := range i { 139 | if n > 0 { 140 | s += ", " 141 | } 142 | s += k.String() 143 | n++ 144 | } 145 | s += "}" 146 | return s 147 | } 148 | 149 | func (i InstanceIDSet) MarshalJSON() ([]byte, error) { 150 | return json.Marshal(i.List()) 151 | } 152 | 153 | type InstanceMap struct { 154 | instMap map[InstanceID]*Instance 155 | lock sync.RWMutex 156 | } 157 | 158 | func NewInstanceMap() *InstanceMap { 159 | return &InstanceMap{instMap: make(map[InstanceID]*Instance)} 160 | } 161 | 162 | func (i *InstanceMap) Add(instance *Instance) { 163 | i.lock.Lock() 164 | defer i.lock.Unlock() 165 | i.instMap[instance.InstanceID] = instance 166 | } 167 | 168 | func (i *InstanceMap) Get(iid InstanceID) *Instance { 169 | i.lock.RLock() 170 | defer i.lock.RUnlock() 171 | return i.instMap[iid] 172 | } 173 | 174 | // gets an existing instance matching the given instance's iid, or adds the 175 | // given instance to the map. The initialize function, if not nil, is run 176 | // on the instance if it's being added to the map, before the map locks 177 | // are released 178 | func (i *InstanceMap) GetOrSet(inst *Instance, initialize func(*Instance)) (*Instance, bool) { 179 | if instance := i.Get(inst.InstanceID); instance != nil { 180 | return instance, true 181 | } 182 | 183 | i.lock.Lock() 184 | defer i.lock.Unlock() 185 | 186 | instance := i.instMap[inst.InstanceID] 187 | existed := true 188 | if instance == nil { 189 | if initialize != nil { 190 | initialize(inst) 191 | } 192 | i.instMap[inst.InstanceID] = inst 193 | existed = false 194 | instance = inst 195 | } 196 | return instance, existed 197 | } 198 | 199 | func (i *InstanceMap) Len() int { 200 | i.lock.RLock() 201 | defer i.lock.RUnlock() 202 | return len(i.instMap) 203 | } 204 | 205 | func (i *InstanceMap) Remove(instance *Instance) { 206 | i.lock.Lock() 207 | defer i.lock.Unlock() 208 | delete(i.instMap, instance.InstanceID) 209 | } 210 | 211 | func (i *InstanceMap) RemoveID(id InstanceID) { 212 | i.lock.Lock() 213 | defer i.lock.Unlock() 214 | delete(i.instMap, id) 215 | } 216 | 217 | func (i *InstanceMap) ContainsID(id InstanceID) bool { 218 | i.lock.RLock() 219 | defer i.lock.RUnlock() 220 | _, exists := i.instMap[id] 221 | return exists 222 | } 223 | 224 | func (i *InstanceMap) Contains(instance *Instance) bool { 225 | i.lock.RLock() 226 | defer i.lock.RUnlock() 227 | return i.ContainsID(instance.InstanceID) 228 | } 229 | 230 | func (i *InstanceMap) InstanceIDs() []InstanceID { 231 | i.lock.RLock() 232 | defer i.lock.RUnlock() 233 | arr := make([]InstanceID, 0, len(i.instMap)) 234 | for key := range i.instMap { 235 | arr = append(arr, key) 236 | } 237 | return arr 238 | } 239 | 240 | func (i *InstanceMap) Instances() []*Instance { 241 | i.lock.RLock() 242 | defer i.lock.RUnlock() 243 | arr := make([]*Instance, 0, len(i.instMap)) 244 | for _, instance := range i.instMap { 245 | arr = append(arr, instance) 246 | } 247 | return arr 248 | } 249 | 250 | // adds the instances of the given instance ids to the given instance map, and returns it 251 | // if the given map is nil, a new map is allocated and returned 252 | func (i *InstanceMap) GetMap(imap map[InstanceID]*Instance, iids []InstanceID) map[InstanceID]*Instance { 253 | if imap == nil { 254 | imap = make(map[InstanceID]*Instance, len(iids)) 255 | } 256 | 257 | i.lock.RLock() 258 | defer i.lock.RUnlock() 259 | 260 | for _, iid := range iids { 261 | imap[iid] = i.instMap[iid] 262 | } 263 | 264 | return imap 265 | } 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/consensus/instance_test.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "launchpad.net/gocheck" 11 | ) 12 | 13 | import ( 14 | "node" 15 | "store" 16 | ) 17 | 18 | type InstanceSetTest struct { } 19 | 20 | var _ = gocheck.Suite(&InstanceSetTest{}) 21 | 22 | func (s *InstanceSetTest) TestSetEqual(c *gocheck.C) { 23 | ids := []InstanceID{NewInstanceID(), NewInstanceID()} 24 | set0 := NewInstanceIDSet(ids) 25 | set1 := NewInstanceIDSet(ids) 26 | c.Assert(set0.Equal(set1), gocheck.Equals, true) 27 | } 28 | 29 | func (s *InstanceSetTest) TestSetNotEqual(c *gocheck.C) { 30 | ids := []InstanceID{NewInstanceID(), NewInstanceID()} 31 | set0 := NewInstanceIDSet(ids) 32 | ids = append(ids, NewInstanceID()) 33 | set1 := NewInstanceIDSet(ids) 34 | c.Assert(set0.Equal(set1), gocheck.Equals, false) 35 | c.Assert(set1.Equal(set0), gocheck.Equals, false) 36 | } 37 | 38 | func (s *InstanceSetTest) TestSetUnion(c *gocheck.C) { 39 | ids := []InstanceID{NewInstanceID(), NewInstanceID(), NewInstanceID()} 40 | set0 := NewInstanceIDSet(ids[:2]) 41 | set1 := NewInstanceIDSet(ids[1:]) 42 | 43 | c.Assert(len(set0), gocheck.Equals, 2) 44 | c.Assert(len(set1), gocheck.Equals, 2) 45 | 46 | set2 := set0.Union(set1) 47 | c.Assert(len(set2), gocheck.Equals, 3) 48 | } 49 | 50 | type InstanceMapTest struct { } 51 | 52 | var _ = gocheck.Suite(&InstanceMapTest{}) 53 | 54 | func (s *InstanceMapTest) TestGetAndSet(c *gocheck.C) { 55 | imap := NewInstanceMap() 56 | instance1 := makeInstance(node.NewNodeId(), []InstanceID{}) 57 | instance2 := makeInstance(node.NewNodeId(), []InstanceID{}) 58 | instance3 := makeInstance(node.NewNodeId(), []InstanceID{}) 59 | instance4 := makeInstance(node.NewNodeId(), []InstanceID{}) 60 | instance5 := makeInstance(node.NewNodeId(), []InstanceID{}) 61 | 62 | c.Assert(imap.Get(instance1.InstanceID), gocheck.IsNil) 63 | c.Assert(imap.Get(instance2.InstanceID), gocheck.IsNil) 64 | c.Assert(imap.Get(instance3.InstanceID), gocheck.IsNil) 65 | c.Assert(imap.Get(instance4.InstanceID), gocheck.IsNil) 66 | c.Assert(imap.Get(instance5.InstanceID), gocheck.IsNil) 67 | 68 | imap.Add(instance1) 69 | c.Assert(imap.Get(instance1.InstanceID), gocheck.NotNil) 70 | c.Assert(imap.Get(instance2.InstanceID), gocheck.IsNil) 71 | c.Assert(imap.Get(instance3.InstanceID), gocheck.IsNil) 72 | c.Assert(imap.Get(instance4.InstanceID), gocheck.IsNil) 73 | c.Assert(imap.Get(instance5.InstanceID), gocheck.IsNil) 74 | 75 | imap.Add(instance2) 76 | imap.Add(instance3) 77 | imap.Add(instance4) 78 | imap.Add(instance5) 79 | 80 | c.Assert(imap.Get(instance1.InstanceID), gocheck.NotNil) 81 | c.Assert(imap.Get(instance2.InstanceID), gocheck.NotNil) 82 | c.Assert(imap.Get(instance3.InstanceID), gocheck.NotNil) 83 | c.Assert(imap.Get(instance4.InstanceID), gocheck.NotNil) 84 | c.Assert(imap.Get(instance5.InstanceID), gocheck.NotNil) 85 | 86 | c.Assert(imap.Contains(instance1), gocheck.Equals, true) 87 | c.Assert(imap.Contains(instance2), gocheck.Equals, true) 88 | c.Assert(imap.Contains(instance3), gocheck.Equals, true) 89 | c.Assert(imap.Contains(instance4), gocheck.Equals, true) 90 | c.Assert(imap.Contains(instance5), gocheck.Equals, true) 91 | 92 | c.Assert(imap.ContainsID(instance1.InstanceID), gocheck.Equals, true) 93 | c.Assert(imap.ContainsID(instance2.InstanceID), gocheck.Equals, true) 94 | c.Assert(imap.ContainsID(instance3.InstanceID), gocheck.Equals, true) 95 | c.Assert(imap.ContainsID(instance4.InstanceID), gocheck.Equals, true) 96 | c.Assert(imap.ContainsID(instance5.InstanceID), gocheck.Equals, true) 97 | 98 | c.Assert(imap.Len(), gocheck.Equals, 5) 99 | 100 | iidSet := NewInstanceIDSet(imap.InstanceIDs()) 101 | c.Assert(iidSet.Contains(instance1.InstanceID), gocheck.Equals, true) 102 | c.Assert(iidSet.Contains(instance2.InstanceID), gocheck.Equals, true) 103 | c.Assert(iidSet.Contains(instance3.InstanceID), gocheck.Equals, true) 104 | c.Assert(iidSet.Contains(instance4.InstanceID), gocheck.Equals, true) 105 | c.Assert(iidSet.Contains(instance5.InstanceID), gocheck.Equals, true) 106 | 107 | instances := imap.Instances() 108 | iidSet = NewInstanceIDSet([]InstanceID{}) 109 | for _, inst := range instances { 110 | iidSet.Add(inst.InstanceID) 111 | } 112 | c.Assert(iidSet.Contains(instance1.InstanceID), gocheck.Equals, true) 113 | c.Assert(iidSet.Contains(instance2.InstanceID), gocheck.Equals, true) 114 | c.Assert(iidSet.Contains(instance3.InstanceID), gocheck.Equals, true) 115 | c.Assert(iidSet.Contains(instance4.InstanceID), gocheck.Equals, true) 116 | c.Assert(iidSet.Contains(instance5.InstanceID), gocheck.Equals, true) 117 | } 118 | 119 | func (s *InstanceMapTest) TestGetOrSet(c *gocheck.C) { 120 | imap := NewInstanceMap() 121 | instance1 := makeInstance(node.NewNodeId(), []InstanceID{}) 122 | instance2 := makeInstance(node.NewNodeId(), []InstanceID{}) 123 | instance2.InstanceID = instance1.InstanceID 124 | 125 | instance, existed := imap.GetOrSet(instance1, nil) 126 | c.Assert(existed, gocheck.Equals, false) 127 | c.Assert(instance, gocheck.Equals, instance1) 128 | 129 | instance, existed = imap.GetOrSet(instance2, nil) 130 | c.Assert(existed, gocheck.Equals, true) 131 | c.Assert(instance, gocheck.Equals, instance1) 132 | } 133 | 134 | type InstanceSerializationTest struct { } 135 | 136 | var _ = gocheck.Suite(&InstanceSerializationTest{}) 137 | 138 | func (s *InstanceSerializationTest) TestSerialization(c *gocheck.C) { 139 | var err error 140 | buf := &bytes.Buffer{} 141 | src := &Instance{ 142 | InstanceID: NewInstanceID(), 143 | LeaderID: node.NewNodeId(), 144 | Successors: []node.NodeId{node.NewNodeId(), node.NewNodeId(), node.NewNodeId()}, 145 | Command: store.NewInstruction("set", "a", []string{"b", "c"}, time.Now()), 146 | Dependencies: []InstanceID{NewInstanceID(), NewInstanceID()}, 147 | Status: INSTANCE_ACCEPTED, 148 | MaxBallot: uint32(500), 149 | Noop: true, 150 | commitTimeout: time.Now(), 151 | DependencyMatch: true, 152 | ReadOnly: true, 153 | } 154 | writer := bufio.NewWriter(buf) 155 | err = src.Serialize(writer) 156 | c.Assert(err, gocheck.IsNil) 157 | err = writer.Flush() 158 | c.Assert(err, gocheck.IsNil) 159 | 160 | // test num bytes 161 | c.Check(len(buf.Bytes()), gocheck.Equals, src.NumBytes()) 162 | 163 | reader := bufio.NewReader(buf) 164 | dst := &Instance{} 165 | err = dst.Deserialize(reader) 166 | c.Assert(err, gocheck.IsNil) 167 | c.Assert(dst, gocheck.DeepEquals, src) 168 | } 169 | 170 | type InstanceTest struct { } 171 | 172 | var _ = gocheck.Suite(&InstanceTest{}) 173 | 174 | // tests that calling merge attributes fails if the instance 175 | // status id not preaccepted 176 | func (s *InstanceTest) TestMergeAttributesBadStatus(c *gocheck.C) { 177 | var err error 178 | 179 | instance := makeInstance(node.NewNodeId(), []InstanceID{}) 180 | 181 | instance.Status = INSTANCE_PREACCEPTED 182 | _, err = instance.mergeAttributes([]InstanceID{}) 183 | c.Assert(err, gocheck.IsNil) 184 | 185 | instance.Status = INSTANCE_ACCEPTED 186 | _, err = instance.mergeAttributes([]InstanceID{}) 187 | c.Assert(err, gocheck.NotNil) 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/consensus/manager_commit.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import ( 8 | "node" 9 | ) 10 | 11 | func makeExecuteTimeout() time.Time { 12 | return time.Now().Add(time.Duration(EXECUTE_TIMEOUT) * time.Millisecond) 13 | } 14 | 15 | // sets the given instance as committed 16 | // in the case of handling messages from leaders to replicas 17 | // the message instance should be passed in. It will either 18 | // update the existing instance in place, or add the message 19 | // instance to the manager's instance 20 | // returns a bool indicating that the instance was actually 21 | // accepted (and not skipped), and an error, if applicable 22 | func (m *Manager) commitInstanceUnsafe(inst *Instance, incrementBallot bool) error { 23 | return m.commitInstance(inst, incrementBallot) 24 | } 25 | 26 | // sets the given instance as committed 27 | // in the case of handling messages from leaders to replicas 28 | // the message instance should be passed in. It will either 29 | // update the existing instance in place, or add the message 30 | // instance to the manager's instance 31 | // returns a bool indicating that the instance was actually 32 | // accepted (and not skipped), and an error, if applicable 33 | func (m *Manager) commitInstance(inst *Instance, incrementBallot bool) error { 34 | start := time.Now() 35 | defer m.statsTiming("commit.instance.time", start) 36 | m.statsInc("commit.instance.count", 1) 37 | instance, existed := m.getOrSetInstance(inst) 38 | 39 | if existed { 40 | logger.Debug("Commit: committing existing instance %v", inst.InstanceID) 41 | } else { 42 | logger.Debug("Commit: committing new instance %v", inst.InstanceID) 43 | m.statsInc("commit.instance.new", 1) 44 | } 45 | 46 | if err := instance.commit(inst, incrementBallot); err != nil { 47 | m.statsInc("commit.instance.error", 1) 48 | return err 49 | } 50 | 51 | if err := m.depsMngr.ReportAcknowledged(instance); err != nil { 52 | return err 53 | } 54 | 55 | if err := m.Persist(); err != nil { 56 | m.statsInc("commit.instance.error", 1) 57 | return err 58 | } 59 | 60 | // wake up any goroutines waiting on this instance 61 | instance.broadcastCommitEvent() 62 | 63 | logger.Debug("Commit: success for Instance: %v", instance.InstanceID) 64 | return nil 65 | } 66 | 67 | // Mark the instance as committed locally, persist state to disk, then send 68 | // commit messages to the other replicas 69 | // We're not concerned with whether the replicas actually respond because, since 70 | // a quorum of nodes have agreed on the dependency graph for this instance, they 71 | // won't be able to do anything else without finding out about it. This method 72 | // will only return an error if persisting the committed state fails 73 | func (m *Manager) sendCommit(instance *Instance, replicas []node.Node) error { 74 | start := time.Now() 75 | defer m.statsTiming("commit.message.send.time", start) 76 | m.statsInc("commit.message.send.count", 1) 77 | 78 | instanceCopy, err := instance.Copy() 79 | if err != nil { 80 | return err 81 | } 82 | msg := &CommitRequest{Instance: instanceCopy} 83 | sendCommitMessage := func(n node.Node) { 84 | if _, err := n.SendMessage(msg); err != nil { 85 | logger.Critical("Error sending commit message: %v", err) 86 | } 87 | } 88 | for _, replica := range replicas { 89 | go sendCommitMessage(replica) 90 | } 91 | return nil 92 | } 93 | 94 | var managerCommitPhase = func(m *Manager, instance *Instance) error { 95 | start := time.Now() 96 | defer m.statsTiming("commit.phase.time", start) 97 | m.statsInc("commit.phase.count", 1) 98 | 99 | logger.Debug("Commit phase started for %v", instance.InstanceID) 100 | replicas := m.getInstanceReplicas(instance) 101 | 102 | if err := m.commitInstance(instance, true); err != nil { 103 | if _, ok := err.(InvalidStatusUpdateError); !ok { 104 | return err 105 | } 106 | } 107 | 108 | // the given instance is out of date if it was new to this 109 | // node, switch over to the local instance 110 | instance = m.instances.Get(instance.InstanceID) 111 | 112 | if err := m.sendCommit(instance, replicas); err != nil { 113 | return err 114 | } 115 | logger.Debug("Commit phase completed for %v", instance.InstanceID) 116 | return nil 117 | } 118 | 119 | func (m *Manager) commitPhase(instance *Instance) error { 120 | return managerCommitPhase(m, instance) 121 | } 122 | 123 | // handles an commit message from the command leader for an instance 124 | // this executes the replica commit phase for the given instance 125 | func (m *Manager) HandleCommit(request *CommitRequest) (*CommitResponse, error) { 126 | m.statsInc("commit.message.received", 1) 127 | start := time.Now() 128 | defer m.statsTiming("commit.message.response.time", start) 129 | 130 | logger.Debug("Commit message received, ballot: %v", request.Instance.MaxBallot) 131 | 132 | // TODO: check ballot 133 | // if instance := s.instances.Get(request.Instance.InstanceID); instance != nil { 134 | // if ballot := instance.getBallot(); ballot >= request.Instance.MaxBallot { 135 | // s.statsInc("commit.message.response.rejected", 1) 136 | // logger.Info("Commit message for %v rejected, %v >= %v", request.Instance.InstanceID, ballot, request.Instance.MaxBallot) 137 | // return &CommitResponse{}, nil 138 | // } 139 | // } 140 | 141 | if err := m.commitInstance(request.Instance, false); err != nil { 142 | if _, ok := err.(InvalidStatusUpdateError); !ok { 143 | m.statsInc("commit.message.response.error", 1) 144 | return nil, err 145 | } 146 | } else { 147 | // asynchronously apply mutation 148 | // TODO: delay based on munber of inProgress instances 149 | // go s.executeInstance(s.instances.Get(request.Instance.InstanceID)) 150 | } 151 | 152 | logger.Debug("Commit message replied") 153 | return &CommitResponse{}, nil 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/consensus/manager_leader_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | tests the command leaders protocol execution 3 | */ 4 | package consensus 5 | 6 | import ( 7 | "launchpad.net/gocheck" 8 | ) 9 | 10 | type LeaderTest struct { 11 | baseManagerTest 12 | } 13 | 14 | var _ = gocheck.Suite(&LeaderTest{}) 15 | 16 | // tests that execution will fail if the command leader 17 | // (the node running ExecuteQuery can't find 18 | // itself in the list of given replicas 19 | func (s *LeaderTest) TestNonReplicaLeaderFailure(c *gocheck.C) { 20 | 21 | } 22 | 23 | // tests that the comand leader aborts if there's an 24 | // error creating a new instance 25 | func (s *LeaderTest) TestInstanceCreationPersistenceError(c *gocheck.C) { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/consensus/testing_checks.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "launchpad.net/gocheck" 5 | ) 6 | 7 | type instMapContainsKeyChecker struct { 8 | *gocheck.CheckerInfo 9 | } 10 | 11 | func (c *instMapContainsKeyChecker) Check(params []interface{}, names []string) (result bool, error string) { 12 | if len(params) != 2 { 13 | return false, "2 arguments required" 14 | } 15 | mapObj, ok := params[0].(*InstanceMap) 16 | if !ok { 17 | return false, "first argument is not an InstanceMap" 18 | } 19 | expectedKey, ok := params[1].(InstanceID) 20 | if !ok { 21 | return false, "second argument is not an InstanceID" 22 | } 23 | instance := mapObj.Get(expectedKey) 24 | if instance == nil { 25 | return false, "" 26 | } 27 | return true, "" 28 | } 29 | 30 | var instMapContainsKey gocheck.Checker = &instMapContainsKeyChecker{ 31 | &gocheck.CheckerInfo{ 32 | Name: "mapContainsKey", 33 | Params: []string{"map", "expected key"}, 34 | }, 35 | } 36 | 37 | type instIdSliceContainsChecker struct { 38 | *gocheck.CheckerInfo 39 | } 40 | 41 | func (c *instIdSliceContainsChecker) Check(params []interface{}, names []string) (result bool, error string) { 42 | if len(params) != 2 { 43 | return false, "2 arguments required" 44 | } 45 | sliceObj, ok := params[0].([]InstanceID) 46 | if !ok { 47 | return false, "first argument is not of type []InstanceID)" 48 | } 49 | expectedKey, ok := params[1].(InstanceID) 50 | if !ok { 51 | return false, "second argument is not an InstanceID" 52 | } 53 | for _, obj := range sliceObj { 54 | if obj == expectedKey { 55 | return true, "" 56 | } 57 | } 58 | return false, "" 59 | } 60 | 61 | var instIdSliceContains gocheck.Checker = &instIdSliceContainsChecker{ 62 | &gocheck.CheckerInfo{ 63 | Name: "sliceContains", 64 | Params: []string{"slice", "expected element"}, 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /src/consensus/testing_setup.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "launchpad.net/gocheck" 11 | ) 12 | 13 | import ( 14 | "node" 15 | "partitioner" 16 | "store" 17 | "topology" 18 | ) 19 | 20 | func setBreakpoint() { 21 | runtime.Breakpoint() 22 | } 23 | 24 | func setupDeps(manager *Manager) { 25 | seq := uint64(0) 26 | for i := 0; i < 4; i++ { 27 | seq++ 28 | instance := manager.makeInstance(getBasicInstruction()) 29 | instance.Status = INSTANCE_EXECUTED 30 | instance.Dependencies, _ = manager.getInstanceDeps(instance) 31 | manager.instances.Add(instance) 32 | manager.executed = append(manager.executed, instance.InstanceID) 33 | } 34 | for i := 0; i < 4; i++ { 35 | seq++ 36 | instance := manager.makeInstance(getBasicInstruction()) 37 | instance.Status = INSTANCE_COMMITTED 38 | instance.Dependencies, _ = manager.getInstanceDeps(instance) 39 | manager.instances.Add(instance) 40 | } 41 | for i := 0; i < 4; i++ { 42 | seq++ 43 | instance := manager.makeInstance(getBasicInstruction()) 44 | if i > 1 { 45 | instance.Status = INSTANCE_ACCEPTED 46 | } else { 47 | instance.Status = INSTANCE_PREACCEPTED 48 | } 49 | instance.Dependencies, _ = manager.getInstanceDeps(instance) 50 | manager.instances.Add(instance) 51 | } 52 | } 53 | 54 | func setupEmptyManager() *Manager { 55 | return NewManager( 56 | topology.NewTopology( 57 | node.NewNodeId(), 58 | topology.DatacenterID("DC1"), 59 | partitioner.NewMD5Partitioner(), 60 | 3, 61 | ), 62 | newMockStore(), 63 | ) 64 | } 65 | func setupManager() *Manager { 66 | manager := setupEmptyManager() 67 | setupDeps(manager) 68 | return manager 69 | } 70 | 71 | // returns a set of mock nodes of the given size 72 | func setupReplicaSet(size int) []*mockNode { 73 | replicas := make([]*mockNode, size) 74 | for i := 0; i < size; i++ { 75 | replicas[i] = newMockNode() 76 | } 77 | nodes := make([]node.Node, size) 78 | for i:=0; i 2, gocheck.Equals, true) 171 | s.nodes = setupReplicaSet(s.numNodes) 172 | s.managers = make([]*Manager, s.numNodes) 173 | s.replicaManagers = make([]*Manager, s.numNodes - 1) 174 | 175 | s.nodeMap = make(map[node.NodeId]*mockNode, s.numNodes) 176 | for _, n := range s.nodes { 177 | s.nodeMap[n.id] = n 178 | } 179 | 180 | s.leader = s.nodes[0] 181 | s.replicas = s.nodes[1:] 182 | 183 | s.manager = s.leader.manager 184 | s.manager.stats = newMockStatter() 185 | for i, n := range s.nodes { 186 | s.managers[i] = n.manager 187 | } 188 | for i, n := range s.replicas { 189 | s.replicaManagers[i] = n.manager 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/disk/disk.go: -------------------------------------------------------------------------------- 1 | package disk 2 | 3 | -------------------------------------------------------------------------------- /src/kvstore/cmd_del.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func (s *KVStore) validateDel(key string, args []string, timestamp time.Time) error { 9 | _ = key 10 | if len(args) != 0 { 11 | return fmt.Errorf("DEL takes 0 args, %v found", len(args)) 12 | } 13 | if timestamp.IsZero() { 14 | return fmt.Errorf("DEL Got zero timestamp") 15 | } 16 | return nil 17 | } 18 | 19 | // Removes the specified keys. A key is ignored if it does not exist. 20 | // Return value: Integer reply: The number of keys that were removed. 21 | // 22 | // internally, each key is deleted one at a time, and a bool value 23 | // is returned indicating if a key was deleted, and the previos value's 24 | // timestamp if one was found 25 | func (s *KVStore) del(key string, ts time.Time) (*Boolean, error) { 26 | var rval *Boolean 27 | if val, exists := s.data[key]; exists { 28 | s.data[key] = NewTombstone(ts) 29 | rval = NewBoolean(true, val.GetTimestamp()) 30 | } else { 31 | rval = NewBoolean(false, time.Time{}) 32 | } 33 | return rval, nil 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/kvstore/cmd_del_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "store" 8 | "testing_helpers" 9 | ) 10 | 11 | // test that a tombstone value is written 12 | func TestDelExistingVal(t *testing.T) { 13 | r := setupKVStore() 14 | 15 | // write value 16 | if _, err := r.ExecuteInstruction(store.NewInstruction("SET", "a", []string{"b"}, time.Now())); err != nil { 17 | t.Fatalf("Unexpected error setting 'a': %v", err) 18 | } 19 | 20 | // sanity check 21 | oldval, exists := r.data["a"] 22 | if ! exists { 23 | t.Errorf("No value found for 'a'") 24 | } 25 | expected, ok := oldval.(*String) 26 | if !ok { 27 | t.Errorf("actual value of unexpected type: %T", oldval) 28 | } 29 | 30 | // delete value 31 | ts := time.Now() 32 | rawval, err := r.ExecuteInstruction(store.NewInstruction("DEL", "a", []string{}, ts)) 33 | if err != nil { 34 | t.Fatalf("Unexpected error deleting 'a': %v", err) 35 | } 36 | val, ok := rawval.(*Boolean) 37 | if !ok { 38 | t.Fatalf("Unexpected value type: %T", val) 39 | } 40 | 41 | testing_helpers.AssertEqual(t, "value", val.GetValue(), true) 42 | testing_helpers.AssertEqual(t, "time", val.GetTimestamp(), expected.GetTimestamp()) 43 | 44 | // check tombstone 45 | rawval, exists = r.data["a"] 46 | if !exists { 47 | t.Fatalf("Expected tombstone, got nil") 48 | } 49 | tsval, ok := rawval.(*Tombstone) 50 | if !ok { 51 | t.Errorf("tombstone value of unexpected type: %T", rawval) 52 | } 53 | testing_helpers.AssertEqual(t, "time", tsval.GetTimestamp(), ts) 54 | } 55 | 56 | func TestDelNonExistingVal(t *testing.T) { 57 | r := setupKVStore() 58 | 59 | // sanity check 60 | _, exists := r.data["a"] 61 | if exists { 62 | t.Errorf("Value unexpectedly found for 'a'") 63 | } 64 | 65 | // delete value 66 | ts := time.Now() 67 | rawval, err := r.ExecuteInstruction(store.NewInstruction("DEL", "a", []string{}, ts)) 68 | if err != nil { 69 | t.Fatalf("Unexpected error deleting 'a': %v", err) 70 | } 71 | val, ok := rawval.(*Boolean) 72 | if !ok { 73 | t.Fatalf("Unexpected value type: %T", val) 74 | } 75 | 76 | testing_helpers.AssertEqual(t, "value", val.GetValue(), false) 77 | testing_helpers.AssertEqual(t, "time", val.GetTimestamp(), time.Time{}) 78 | 79 | // check tombstone 80 | rawval, exists = r.data["a"] 81 | if exists { 82 | t.Fatalf("Unexpected tombstone val found: %T %v", rawval, rawval) 83 | } 84 | } 85 | 86 | // tests validation of DEL insructions 87 | func TestDelValidation(t *testing.T) { 88 | r := setupKVStore() 89 | 90 | var val store.Value 91 | var err error 92 | 93 | val, err = r.ExecuteInstruction(store.NewInstruction("DEL", "a", []string{"x", "y"}, time.Now())) 94 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 95 | if err == nil { 96 | t.Errorf("Expected error, got nil") 97 | } else { 98 | t.Logf("Got expected err: %v", err) 99 | } 100 | 101 | val, err = r.ExecuteInstruction(store.NewInstruction("DEL", "a", []string{}, time.Time{})) 102 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 103 | if err == nil { 104 | t.Errorf("Expected error, got nil") 105 | } else { 106 | t.Logf("Got expected err: %v", err) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/kvstore/cmd_get.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "store" 5 | "fmt" 6 | ) 7 | 8 | func (s *KVStore) validateGet(key string, args []string) error { 9 | _ = key 10 | if len(args) != 0 { 11 | return fmt.Errorf("too many args for GET") 12 | } 13 | return nil 14 | } 15 | 16 | // Get the value of key. If the key does not exist the special value nil is returned. 17 | // An error is returned if the value stored at key is not a string, because GET only handles string values. 18 | func (s *KVStore) get(key string) (store.Value, error) { 19 | // TODO: check that the returned value is a string or tombstone value 20 | return s.data[key], nil 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/kvstore/cmd_get_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "store" 5 | "testing" 6 | "time" 7 | "testing_helpers" 8 | ) 9 | 10 | func TestGet(t *testing.T) { 11 | r := setupKVStore() 12 | expected := NewString("b", time.Now()) 13 | r.data["a"] = expected 14 | 15 | val, err := r.ExecuteInstruction(store.NewInstruction("GET", "a", []string{}, time.Time{})) 16 | if err != nil { 17 | t.Fatalf("Unexpected error on read: %v", err) 18 | } 19 | actual, ok := val.(*String) 20 | if !ok { 21 | t.Fatalf("Unexpected value type: %T", val) 22 | } 23 | 24 | testing_helpers.AssertEqual(t, "data", expected.GetValue(), actual.GetValue()) 25 | testing_helpers.AssertEqual(t, "time", expected.GetTimestamp(), actual.GetTimestamp()) 26 | } 27 | 28 | // tests that calling get on a key holding a value other than 29 | // a string value returns an error 30 | func TestGetNonStringFails(t *testing.T) { 31 | t.Skipf("other types not implemented yet") 32 | } 33 | 34 | // tests validation of GET insructions 35 | func TestGetValidation(t *testing.T) { 36 | r := setupKVStore() 37 | 38 | // too many args 39 | val, err := r.ExecuteInstruction(store.NewInstruction("GET", "a", []string{"b"}, time.Time{})) 40 | if val != nil { 41 | t.Errorf("Unexpected non-nil value") 42 | } 43 | if err == nil { 44 | t.Errorf("Expected error, got nil") 45 | } else { 46 | t.Logf("Got expected err: %v", err) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/kvstore/cmd_set.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "store" 5 | "time" 6 | "fmt" 7 | ) 8 | 9 | func (s *KVStore) validateSet(key string, args []string, timestamp time.Time) error { 10 | _ = key 11 | if len(args) != 1 { 12 | return fmt.Errorf("incorrect number of args for SET. Expected 1, got %v", len(args)) 13 | } 14 | if timestamp.IsZero() { 15 | return fmt.Errorf("DEL Got zero timestamp") 16 | } 17 | return nil 18 | } 19 | 20 | // Set key to hold the string value. If key already holds a value, it is overwritten, 21 | // regardless of its type. Any previous time to live associated with the key is discarded 22 | // on successful SET operation. 23 | func (s *KVStore) set(key string, val string, ts time.Time) (store.Value) { 24 | existing, exists := s.data[key] 25 | if exists && ts.Before(existing.GetTimestamp()) { 26 | return existing 27 | } 28 | value := NewString(val, ts) 29 | s.data[key] = value 30 | return value 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/kvstore/cmd_set_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "testing_helpers" 6 | "time" 7 | 8 | "store" 9 | ) 10 | 11 | // tests basic function of set 12 | func TestSet(t *testing.T) { 13 | r := setupKVStore() 14 | 15 | // sanity check 16 | _, exists := r.data["a"] 17 | if exists { 18 | t.Fatalf("Unexpectedly found 'a' in store") 19 | } 20 | 21 | ts := time.Now() 22 | rawval, err := r.ExecuteInstruction(store.NewInstruction("SET", "a", []string{"b"}, ts)) 23 | if err != nil { 24 | t.Errorf("Unexpected write error: %v", err) 25 | } 26 | rval, ok := rawval.(*String) 27 | if !ok { 28 | t.Errorf("returned value of unexpected type: %T", rawval) 29 | } 30 | 31 | actualraw, exists := r.data["a"] 32 | if ! exists { 33 | t.Errorf("No value found for 'a'") 34 | } 35 | actual, ok := actualraw.(*String) 36 | if !ok { 37 | t.Errorf("actual value of unexpected type: %T", actualraw) 38 | } 39 | 40 | testing_helpers.AssertEqual(t, "rval data", "b", rval.GetValue()) 41 | testing_helpers.AssertEqual(t, "actual data", "b", actual.GetValue()) 42 | testing_helpers.AssertEqual(t, "rval time", ts, rval.GetTimestamp()) 43 | testing_helpers.AssertEqual(t, "actual time", ts, actual.GetTimestamp()) 44 | } 45 | 46 | // if set is called with a timestamp which is lower than 47 | // the existing value, it should be ignored 48 | func TestSetConflictingTimestamp(t *testing.T) { 49 | r := setupKVStore() 50 | now := time.Now() 51 | then := now.Add(time.Duration(-1)) 52 | expected := r.set("a", "b", now) 53 | actual := r.set("a", "c", then) 54 | testing_helpers.AssertEqual(t, "set val", expected, actual) 55 | 56 | } 57 | 58 | // tests validation of SET insructions 59 | func TestSetValidation(t *testing.T) { 60 | r := setupKVStore() 61 | 62 | var val store.Value 63 | var err error 64 | 65 | val, err = r.ExecuteInstruction(store.NewInstruction("SET", "a", []string{"x", "y"}, time.Now())) 66 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 67 | if err == nil { 68 | t.Errorf("Expected error, got nil") 69 | } else { 70 | t.Logf("Got expected err: %v", err) 71 | } 72 | 73 | val, err = r.ExecuteInstruction(store.NewInstruction("SET", "a", []string{"x"}, time.Time{})) 74 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 75 | if err == nil { 76 | t.Errorf("Expected error, got nil") 77 | } else { 78 | t.Logf("Got expected err: %v", err) 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/kvstore/store.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | import ( 11 | "store" 12 | ) 13 | 14 | 15 | // read instructions 16 | const ( 17 | GET = "GET" 18 | ) 19 | 20 | // write instructions 21 | const ( 22 | SET = "SET" 23 | DEL = "DEL" 24 | ) 25 | 26 | 27 | type KVStore struct { 28 | 29 | data map[string] store.Value 30 | 31 | // TODO: delete 32 | // temporary lock, used until 33 | // things are broken out into 34 | // goroutines 35 | lock sync.RWMutex 36 | 37 | } 38 | 39 | var _ = store.Store(&KVStore{}) 40 | 41 | func NewKVStore() *KVStore { 42 | r := &KVStore{ 43 | data:make(map[string] store.Value), 44 | } 45 | return r 46 | } 47 | 48 | func (s *KVStore) SerializeValue(v store.Value) ([]byte, error) { 49 | buf := &bytes.Buffer{} 50 | if err := WriteValue(buf, v) ; err != nil { return nil, err } 51 | return buf.Bytes(), nil 52 | } 53 | 54 | func (s *KVStore) DeserializeValue(b []byte) (store.Value, store.ValueType, error) { 55 | buf := bytes.NewBuffer(b) 56 | val, vtype, err := ReadValue(buf) 57 | if err != nil { return nil, "", err } 58 | return val, vtype, nil 59 | } 60 | 61 | func (s *KVStore) Start() error { 62 | return nil 63 | } 64 | 65 | func (s *KVStore) Stop() error { 66 | return nil 67 | } 68 | 69 | func (s *KVStore) ExecuteInstruction(instruction store.Instruction) (store.Value, error) { 70 | s.lock.Lock() 71 | defer s.lock.Unlock() 72 | 73 | cmd := instruction.Cmd 74 | key := instruction.Key 75 | args := instruction.Args 76 | timestamp := instruction.Timestamp 77 | 78 | switch cmd { 79 | case GET: 80 | // 81 | if err := s.validateGet(key, args); err != nil { return nil, err } 82 | rval, err := s.get(key) 83 | if err != nil { return nil, err } 84 | return rval, nil 85 | case SET: 86 | if err := s.validateSet(key, args, timestamp); err != nil { return nil, err } 87 | return s.set(key, args[0], timestamp), nil 88 | case DEL: 89 | if err := s.validateDel(key, args, timestamp); err != nil { return nil, err } 90 | return s.del(key, timestamp) 91 | default: 92 | return nil, fmt.Errorf("Unrecognized write command: %v", cmd) 93 | } 94 | return nil, nil 95 | } 96 | 97 | // reconciles multiple values and returns instructions for correcting 98 | // the values on inaccurate nodes 99 | // 100 | // Reconcile should handle value maps with one value without hitting 101 | // a value type specific reconciliation function 102 | // 103 | // value type specific reconciliation functions should be able to handle 104 | // getting unfamiliar types, but can operate under the assumption that if 105 | // they're being called, the oldest timestamp of the given values belongs 106 | // to a value of it's type. 107 | func (s *KVStore) Reconcile(key string, values []store.Value) (store.Value, [][]store.Instruction, error) { 108 | switch len(values){ 109 | case 0: 110 | return nil, nil, fmt.Errorf("At least one value must be provided") 111 | case 1: 112 | var val store.Value 113 | for _, v := range values { val = v } 114 | return val, nil, nil 115 | default: 116 | highValue := getHighValue(values) 117 | 118 | switch highValue.GetValueType() { 119 | case STRING_VALUE: 120 | return reconcileString(key, highValue.(*String), values) 121 | case TOMBSTONE_VALUE: 122 | return reconcileTombstone(key, highValue.(*Tombstone), values) 123 | default: 124 | return nil, [][]store.Instruction{}, fmt.Errorf("Unknown value type: %T", highValue) 125 | } 126 | } 127 | return nil, [][]store.Instruction{}, nil 128 | } 129 | 130 | func (s *KVStore) IsReadOnly(instruction store.Instruction) bool { 131 | switch strings.ToUpper(instruction.Cmd) { 132 | case GET: 133 | return true 134 | } 135 | return false 136 | } 137 | 138 | func (s *KVStore) IsWriteOnly(instruction store.Instruction) bool { 139 | switch strings.ToUpper(instruction.Cmd) { 140 | case SET, DEL: 141 | return true 142 | } 143 | return false 144 | } 145 | 146 | func (s *KVStore) ReturnsValue(cmd string) bool { 147 | switch strings.ToUpper(cmd) { 148 | case GET: 149 | return true 150 | } 151 | return false 152 | } 153 | 154 | func (s *KVStore) InterferingKeys(instruction store.Instruction) []string { 155 | return []string{instruction.Key} 156 | } 157 | 158 | // ----------- data import / export ----------- 159 | 160 | 161 | // blindly gets the contents of the given key 162 | func (s *KVStore) GetRawKey(key string) (store.Value, error) { 163 | val, ok := s.data[key] 164 | if !ok { 165 | return nil, fmt.Errorf("key [%v] does not exist", key) 166 | } 167 | return val, nil 168 | } 169 | 170 | // blindly sets the contents of the given key 171 | func (s *KVStore) SetRawKey(key string, val store.Value) error { 172 | s.data[key] = val 173 | return nil 174 | } 175 | 176 | // returns all of the keys held by the store, including keys containing 177 | // tombstones 178 | func (s *KVStore) GetKeys() []string { 179 | keys := make([]string, 0, len(s.data)) 180 | for key := range s.data { 181 | keys = append(keys, key) 182 | } 183 | return keys 184 | } 185 | 186 | func (s *KVStore) KeyExists(key string) bool { 187 | _, ok := s.data[key] 188 | return ok 189 | } 190 | -------------------------------------------------------------------------------- /src/kvstore/store_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | "store" 9 | ) 10 | 11 | // table of instructions, and whether they're 12 | // a write (true) or read (false) 13 | var isWrite = []struct { 14 | cmd string 15 | result bool 16 | }{ 17 | {"GET", false}, 18 | {"SET", true}, 19 | {"DEL", true}, 20 | } 21 | 22 | func TestIsWriteCmd(t *testing.T) { 23 | r := &KVStore{} 24 | for _, c := range isWrite { 25 | if result := r.IsWriteOnly(store.Instruction{Cmd:c.cmd}); result != c.result { 26 | if result { 27 | t.Errorf("%v erroneously identified as a write", c.cmd) 28 | } else { 29 | t.Errorf("%v not identified as a write", c.cmd) 30 | } 31 | } 32 | } 33 | } 34 | 35 | func TestIsReadCmd(t *testing.T) { 36 | r := &KVStore{} 37 | for _, c := range isWrite { 38 | if result := r.IsReadOnly(store.Instruction{Cmd:c.cmd}); result != !c.result { 39 | if result { 40 | t.Errorf("%v erroneously identified as a read", c.cmd) 41 | } else { 42 | t.Errorf("%v not identified as a read", c.cmd) 43 | } 44 | } 45 | } 46 | } 47 | 48 | // ----------- data import / export ----------- 49 | 50 | func TestGetRawKeySuccess(t *testing.T) { 51 | r := setupKVStore() 52 | expected, err := r.ExecuteInstruction(store.NewInstruction("SET", "a", []string{"b"}, time.Now())) 53 | if err != nil { 54 | t.Fatalf("Unexpected error executing set: %v", err) 55 | } 56 | 57 | rval, err := r.GetRawKey("a") 58 | if err != nil { 59 | t.Fatal("unexpectedly got error: %v", err) 60 | } 61 | val, ok := rval.(*String) 62 | if !ok { 63 | t.Fatal("expected value of type stringValue, got %T", rval) 64 | } 65 | testing_helpers.AssertEqual(t, "value", expected, val) 66 | } 67 | 68 | func TestSetRawKey(t *testing.T) { 69 | r := setupKVStore() 70 | expected := NewBoolean(true, time.Now()) 71 | r.SetRawKey("x", expected) 72 | 73 | rval, exists := r.data["x"] 74 | if !exists { 75 | t.Fatalf("no value found for key 'x'") 76 | } 77 | val, ok := rval.(*Boolean) 78 | if !ok { 79 | t.Fatal("expected value of type boolValues, got %T", rval) 80 | } 81 | 82 | testing_helpers.AssertEqual(t, "val", expected, val) 83 | } 84 | 85 | func TestGetKeys(t *testing.T) { 86 | r := setupKVStore() 87 | val := NewBoolean(true, time.Now()) 88 | r.SetRawKey("x", val) 89 | r.SetRawKey("y", val) 90 | r.SetRawKey("z", val) 91 | 92 | // make set of expected keys 93 | expected := map[string]bool {"x": true, "y": true, "z": true} 94 | 95 | seen := make(map[string] bool) 96 | keys := r.GetKeys() 97 | testing_helpers.AssertEqual(t, "num keys", len(expected), len(keys)) 98 | for _, key := range keys { 99 | if expected[key] && !seen[key] { 100 | seen[key] = true 101 | } else if seen[key] { 102 | t.Errorf("Key '%v' seen more than once", key) 103 | } else { 104 | t.Errorf("Unexpected key returned: '%v'", key) 105 | } 106 | } 107 | } 108 | 109 | func TestKeyExists(t *testing.T) { 110 | r := setupKVStore() 111 | val := NewBoolean(true, time.Now()) 112 | r.SetRawKey("x", val) 113 | 114 | testing_helpers.AssertEqual(t, "existing key", true, r.KeyExists("x")) 115 | testing_helpers.AssertEqual(t, "existing key", false, r.KeyExists("y")) 116 | } 117 | 118 | // tests that attempting to reconcile an empty array 119 | // returns an error 120 | func TestReconcilingEmptySlice(t *testing.T) { 121 | vmap := []store.Value{} 122 | ractual, adjustments, err := setupKVStore().Reconcile("k", vmap) 123 | 124 | if err == nil { 125 | t.Fatalf("expected reconciliation error") 126 | } else { 127 | t.Log(err) 128 | } 129 | testing_helpers.AssertEqual(t, "returned val", nil, ractual) 130 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 131 | } 132 | 133 | // tests reconciling a value map with only a single element 134 | func TestReconcileSingleValue(t *testing.T) { 135 | ts0 := time.Now() 136 | expected := NewString("a", ts0) 137 | vmap := []store.Value{expected} 138 | ractual, adjustments, err := setupKVStore().Reconcile("k", vmap) 139 | 140 | if err != nil { 141 | t.Fatalf("unexpected reconciliation error: %v", err) 142 | } 143 | 144 | actual, ok := ractual.(*String) 145 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 146 | 147 | assertEqualValue(t, "reconciled value", expected, actual) 148 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/kvstore/testing.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "store" 6 | ) 7 | 8 | // returns a redis instance with defaults set 9 | func setupKVStore() *KVStore { 10 | return NewKVStore() 11 | } 12 | 13 | func assertEqualValue(t *testing.T, name string, v1 store.Value, v2 store.Value) { 14 | if !v1.Equal(v2) { 15 | t.Errorf("\x1b[1m\x1b[35m%v mismatch. Expecting [%v], got [%v]\x1b[0m", name, v1, v2) 16 | } else { 17 | t.Logf("%v OK: [%v]", name, v1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/kvstore/val_bool.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "serializer" 8 | "store" 9 | ) 10 | 11 | type Boolean struct { 12 | value bool 13 | time time.Time 14 | } 15 | 16 | func NewBoolean(val bool, timestamp time.Time) *Boolean { 17 | v := &Boolean{ 18 | value:val, 19 | time:timestamp, 20 | } 21 | return v 22 | } 23 | 24 | func (v *Boolean) GetValue() bool { 25 | return v.value 26 | } 27 | 28 | func (v *Boolean) GetTimestamp() time.Time { 29 | return v.time 30 | } 31 | 32 | func (v *Boolean) GetValueType() store.ValueType { 33 | return BOOL_VALUE 34 | } 35 | 36 | func (v *Boolean) Equal(o store.Value) bool { 37 | if !baseValueEqual(v, o) { return false } 38 | other := o.(*Boolean) 39 | if v.value != other.value { return false } 40 | return true 41 | } 42 | 43 | func (v *Boolean) Serialize(buf *bufio.Writer) error { 44 | var b byte 45 | if v.value { b = 0xff } 46 | if err := buf.WriteByte(b); err != nil { 47 | return err 48 | } 49 | if err := serializer.WriteTime(buf, v.time); err != nil { 50 | return err 51 | } 52 | if err := buf.Flush(); err != nil { 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | func (v *Boolean) Deserialize(buf *bufio.Reader) error { 59 | if b, err := buf.ReadByte(); err != nil { 60 | return err 61 | } else { 62 | v.value = b != 0x00 63 | } 64 | if t, err := serializer.ReadTime(buf); err != nil { 65 | return err 66 | } else { 67 | v.time = t 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /src/kvstore/val_bool_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | ) 9 | 10 | func TestBooleanValue(t *testing.T) { 11 | s := setupKVStore() 12 | for _, val := range []bool{true, false} { 13 | src := NewBoolean(val, time.Now()) 14 | 15 | b, err := s.SerializeValue(src) 16 | if err != nil { 17 | t.Fatalf("Unexpected serialization error: %v", err) 18 | } 19 | 20 | val, vtype, err := s.DeserializeValue(b) 21 | if err != nil { 22 | t.Fatalf("Unexpected deserialization error: %v", err) 23 | } 24 | if vtype != BOOL_VALUE { 25 | t.Fatalf("Unexpected value type enum: %v", vtype) 26 | } 27 | dst, ok := val.(*Boolean) 28 | if !ok { 29 | t.Fatalf("Unexpected value type: %T", val) 30 | } 31 | 32 | testing_helpers.AssertEqual(t, "value", src.value, dst.value) 33 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 34 | } 35 | } 36 | 37 | // tests the boolean value's equality method 38 | func TestBooleanEquality(t *testing.T) { 39 | t0 := time.Now() 40 | v0 := NewBoolean(true, t0) 41 | 42 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewBoolean(true, t0))) 43 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewBoolean(true, t0.Add(4)))) 44 | testing_helpers.AssertEqual(t, "unequal value", false, v0.Equal(NewBoolean(false, t0))) 45 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewString("asdf", t0))) 46 | } 47 | -------------------------------------------------------------------------------- /src/kvstore/val_string.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "store" 8 | "serializer" 9 | ) 10 | 11 | // a single value used for 12 | // key/val types 13 | type String struct { 14 | value string 15 | time time.Time 16 | } 17 | 18 | // single value constructor 19 | func NewString(value string, time time.Time) *String { 20 | v := &String{ 21 | value:value, 22 | time:time, 23 | } 24 | return v 25 | } 26 | 27 | func (v *String) GetValue() string { 28 | return v.value 29 | } 30 | 31 | func (v *String) GetTimestamp() time.Time { 32 | return v.time 33 | } 34 | 35 | func (v *String) GetValueType() store.ValueType { 36 | return STRING_VALUE 37 | } 38 | 39 | func (v *String) Equal(o store.Value) bool { 40 | if !baseValueEqual(v, o) { return false } 41 | other := o.(*String) 42 | if v.value != other.value { return false } 43 | return true 44 | } 45 | 46 | func (v *String) Serialize(buf *bufio.Writer) error { 47 | if err := serializer.WriteFieldBytes(buf, []byte(v.value)); err != nil { 48 | return err 49 | } 50 | if err := serializer.WriteTime(buf, v.time); err != nil { 51 | return err 52 | } 53 | if err := buf.Flush(); err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | 59 | func (v *String) Deserialize(buf *bufio.Reader) error { 60 | if val, err := serializer.ReadFieldBytes(buf); err != nil { 61 | return err 62 | } else { 63 | v.value = string(val) 64 | } 65 | 66 | if t, err := serializer.ReadTime(buf); err != nil { 67 | return err 68 | } else { 69 | v.time = t 70 | } 71 | return nil 72 | } 73 | 74 | func reconcileString(key string, highValue *String, values []store.Value) (*String, [][]store.Instruction, error) { 75 | // create instructions for the unequal nodes 76 | instructions := make([][]store.Instruction, len(values)) 77 | for i, val := range values { 78 | if !highValue.Equal(val) { 79 | instructions[i] = []store.Instruction{store.Instruction{ 80 | Cmd:"SET", 81 | Key:key, 82 | Args:[]string{highValue.value}, 83 | Timestamp:highValue.time, 84 | }} 85 | } 86 | } 87 | 88 | return highValue, instructions, nil 89 | } 90 | -------------------------------------------------------------------------------- /src/kvstore/val_string_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "store" 8 | "testing_helpers" 9 | ) 10 | 11 | // tests the string value 12 | func TestStringValue(t *testing.T) { 13 | s := setupKVStore() 14 | src := NewString("blake", time.Now()) 15 | 16 | b, err := s.SerializeValue(src) 17 | if err != nil { 18 | t.Fatalf("Unexpected serialization error: %v", err) 19 | } 20 | 21 | val, vtype, err := s.DeserializeValue(b) 22 | if err != nil { 23 | t.Fatalf("Unexpected deserialization error: %v", err) 24 | } 25 | if vtype != STRING_VALUE { 26 | t.Fatalf("Unexpected value type enum: %v", vtype) 27 | } 28 | dst, ok := val.(*String) 29 | if !ok { 30 | t.Fatalf("Unexpected value type: %T", val) 31 | } 32 | 33 | testing_helpers.AssertEqual(t, "value", src.value, dst.value) 34 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 35 | } 36 | 37 | // tests that the tombstone struct satisfies the 38 | // value interface 39 | func TestStringInterface(_ *testing.T) { 40 | func (store.Value){}(NewTombstone(time.Now())) 41 | } 42 | 43 | // tests that mismatched values are reconciled and 44 | // corrected as expected 45 | func TestStringMismatchReconciliation(t *testing.T) { 46 | ts0 := time.Now() 47 | ts1 := ts0.Add(time.Duration(-3000)) 48 | expected := NewString("a", ts0) 49 | values := []store.Value{expected, NewString("b", ts1), expected } 50 | 51 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 52 | 53 | if err != nil { 54 | t.Fatalf("unexpected reconciliation error: %v", err) 55 | } 56 | 57 | actual, ok := ractual.(*String) 58 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 59 | 60 | assertEqualValue(t, "reconciled value", expected, actual) 61 | testing_helpers.AssertEqual(t, "adjustment size", len(values), len(adjustments)) 62 | 63 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[0])) 64 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[2])) 65 | 66 | instructions := adjustments[1] 67 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 68 | 69 | instruction := instructions[0] 70 | expected_instr := store.Instruction{Cmd:"SET", Key:"k", Args:[]string{"a"}, Timestamp:ts0} 71 | if !expected_instr.Equal(instruction) { 72 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 73 | } 74 | } 75 | 76 | // should set values of different types to the value 77 | // with the largest timestamp 78 | func TestStringMultiTypeReconciliation(t *testing.T) { 79 | ts0 := time.Now() 80 | ts1 := ts0.Add(time.Duration(-3000)) 81 | expected := NewString("a", ts0) 82 | values := []store.Value {expected, NewTombstone(ts1)} 83 | 84 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 85 | 86 | if err != nil { 87 | t.Fatalf("unexpected reconciliation error: %v", err) 88 | } 89 | 90 | actual, ok := ractual.(*String) 91 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 92 | 93 | assertEqualValue(t, "reconciled value", expected, actual) 94 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[0])) 95 | 96 | instructions := adjustments[1] 97 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 98 | 99 | instruction := instructions[0] 100 | expected_instr := store.Instruction{Cmd:"SET", Key:"k", Args:[]string{"a"}, Timestamp:ts0} 101 | if !expected_instr.Equal(instruction) { 102 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 103 | } 104 | } 105 | 106 | // should return the correct value and no adjustment 107 | // instructions if all of the values match 108 | func TestStringNoOpReconciliation(t *testing.T) { 109 | ts0 := time.Now() 110 | expected := NewString("a", ts0) 111 | values := []store.Value {expected, expected, expected} 112 | 113 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 114 | 115 | if err != nil { 116 | t.Fatalf("unexpected reconciliation error: %v", err) 117 | } 118 | 119 | actual, ok := ractual.(*String) 120 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 121 | 122 | assertEqualValue(t, "reconciled value", expected, actual) 123 | testing_helpers.AssertEqual(t, "adjustment size", len(values), len(adjustments)) 124 | for _, adjustment := range adjustments { 125 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustment)) 126 | } 127 | } 128 | 129 | // tests the boolean value's equality method 130 | func TestStringEquality(t *testing.T) { 131 | t0 := time.Now() 132 | v0 := NewString("abc", t0) 133 | 134 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewString("abc", t0))) 135 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewString("abc", t0.Add(4)))) 136 | testing_helpers.AssertEqual(t, "unequal value", false, v0.Equal(NewString("def", t0))) 137 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewTombstone(t0))) 138 | } 139 | -------------------------------------------------------------------------------- /src/kvstore/val_tombstone.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "serializer" 8 | "store" 9 | ) 10 | 11 | // a value indicating a deletion 12 | type Tombstone struct { 13 | time time.Time 14 | } 15 | 16 | // single value constructor 17 | func NewTombstone(time time.Time) *Tombstone { 18 | v := &Tombstone{ 19 | time:time, 20 | } 21 | return v 22 | } 23 | 24 | func (v *Tombstone) GetTimestamp() time.Time { 25 | return v.time 26 | } 27 | 28 | func (v *Tombstone) GetValueType() store.ValueType { 29 | return TOMBSTONE_VALUE 30 | } 31 | 32 | func (v *Tombstone) Equal(o store.Value) bool { 33 | return baseValueEqual(v, o) 34 | } 35 | 36 | func (v *Tombstone) Serialize(buf *bufio.Writer) error { 37 | if err := serializer.WriteTime(buf, v.time); err != nil { 38 | return err 39 | } 40 | if err := buf.Flush(); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (v *Tombstone) Deserialize(buf *bufio.Reader) error { 47 | if t, err := serializer.ReadTime(buf); err != nil { 48 | return err 49 | } else { 50 | v.time = t 51 | } 52 | return nil 53 | } 54 | 55 | func reconcileTombstone(key string, highValue *Tombstone, values []store.Value) (*Tombstone, [][]store.Instruction, error) { 56 | // create instructions for the unequal nodes 57 | instructions := make([][]store.Instruction, len(values)) 58 | for i, val := range values { 59 | if val != highValue { 60 | instructions[i] = []store.Instruction{store.Instruction{ 61 | Cmd:"DEL", 62 | Key:key, 63 | Args:[]string{}, 64 | Timestamp:highValue.time, 65 | }} 66 | } 67 | } 68 | 69 | return highValue, instructions, nil 70 | } 71 | -------------------------------------------------------------------------------- /src/kvstore/val_tombstone_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | "store" 9 | ) 10 | 11 | func TestTombstoneValue(t *testing.T) { 12 | s := setupKVStore() 13 | src := NewTombstone(time.Now()) 14 | 15 | b, err := s.SerializeValue(src) 16 | if err != nil { 17 | t.Fatalf("Unexpected serialization error: %v", err) 18 | } 19 | 20 | val, vtype, err := s.DeserializeValue(b) 21 | if err != nil { 22 | t.Fatalf("Unexpected deserialization error: %v", err) 23 | } 24 | if vtype != TOMBSTONE_VALUE { 25 | t.Fatalf("Unexpected value type enum: %v", vtype) 26 | } 27 | dst, ok := val.(*Tombstone) 28 | if !ok { 29 | t.Fatalf("Unexpected value type: %T", val) 30 | } 31 | 32 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 33 | } 34 | 35 | // tests that the tombstone struct satisfies the 36 | // value interface 37 | func TestTombstoneInterface(t *testing.T) { 38 | func (store.Value){}(NewTombstone(time.Now())) 39 | } 40 | 41 | // tests that mismatched values are reconciled and 42 | // corrected as expected 43 | func TestTombstoneMismatchReconciliation(t *testing.T) { 44 | ts0 := time.Now() 45 | ts1 := ts0.Add(time.Duration(-3000)) 46 | expected := NewTombstone(ts0) 47 | values := []store.Value{expected, NewTombstone(ts1), expected} 48 | 49 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 50 | 51 | if err != nil { 52 | t.Fatalf("unexpected reconciliation error: %v", err) 53 | } 54 | 55 | actual, ok := ractual.(*Tombstone) 56 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 57 | 58 | assertEqualValue(t, "reconciled value", expected, actual) 59 | testing_helpers.AssertEqual(t, "adjustment size", len(values), len(adjustments)) 60 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[0])) 61 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[2])) 62 | 63 | instructions := adjustments[1] 64 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 65 | 66 | instruction := instructions[0] 67 | expected_instr := store.Instruction{Cmd:"DEL", Key:"k", Args:[]string{}, Timestamp:ts0} 68 | if !expected_instr.Equal(instruction) { 69 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 70 | } 71 | } 72 | 73 | // should set values of different types to the value 74 | // with the largest timestamp 75 | func TestTombstoneMultiTypeReconciliation(t *testing.T) { 76 | ts0 := time.Now() 77 | ts1 := ts0.Add(time.Duration(-3000)) 78 | expected := NewTombstone(ts0) 79 | values := []store.Value{expected, NewString("a", ts1)} 80 | 81 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 82 | 83 | if err != nil { 84 | t.Fatalf("unexpected reconciliation error: %v", err) 85 | } 86 | 87 | actual, ok := ractual.(*Tombstone) 88 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 89 | 90 | assertEqualValue(t, "reconciled value", expected, actual) 91 | testing_helpers.AssertEqual(t, "adjustment size", len(values), len(adjustments)) 92 | testing_helpers.AssertEqual(t, "num instructions", 0, len(adjustments[0])) 93 | 94 | instructions := adjustments[1] 95 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 96 | 97 | instruction := instructions[0] 98 | expected_instr := store.Instruction{Cmd:"DEL", Key:"k", Args:[]string{}, Timestamp:ts0} 99 | if !expected_instr.Equal(instruction) { 100 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 101 | } 102 | } 103 | 104 | // should return the correct value and no adjustment 105 | // instructions if all of the values match 106 | func TestTombstoneNoOpReconciliation(t *testing.T) { 107 | ts0 := time.Now() 108 | expected := NewTombstone(ts0) 109 | values := []store.Value {expected, expected, expected} 110 | 111 | ractual, adjustments, err := setupKVStore().Reconcile("k", values) 112 | 113 | if err != nil { 114 | t.Fatalf("unexpected reconciliation error: %v", err) 115 | } 116 | 117 | actual, ok := ractual.(*Tombstone) 118 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 119 | 120 | assertEqualValue(t, "reconciled value", expected, actual) 121 | testing_helpers.AssertEqual(t, "adjustment size", len(values), len(adjustments)) 122 | for _, adjustment := range adjustments { 123 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustment)) 124 | } 125 | } 126 | 127 | // tests the tombstone value's equality method 128 | func TestTombstoneEquality(t *testing.T) { 129 | t0 := time.Now() 130 | v0 := NewTombstone(t0) 131 | 132 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewTombstone(t0))) 133 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewTombstone(t0.Add(4)))) 134 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewString("asdf", t0))) 135 | } 136 | -------------------------------------------------------------------------------- /src/kvstore/values.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "serializer" 10 | "store" 11 | ) 12 | 13 | const ( 14 | STRING_VALUE = store.ValueType("STRING") 15 | TOMBSTONE_VALUE = store.ValueType("TOMBSTONE") 16 | BOOL_VALUE = store.ValueType("BOOL") 17 | ) 18 | 19 | func WriteValue(buf io.Writer, v store.Value) error { 20 | writer := bufio.NewWriter(buf) 21 | 22 | vtype := v.GetValueType() 23 | if err := serializer.WriteFieldBytes(writer, []byte(vtype)); err != nil { return err } 24 | if err := v.Serialize(writer); err != nil { return err } 25 | if err := writer.Flush(); err != nil { return err } 26 | return nil 27 | } 28 | 29 | func ReadValue(buf io.Reader) (store.Value, store.ValueType, error) { 30 | reader := bufio.NewReader(buf) 31 | vstr, err := serializer.ReadFieldBytes(reader) 32 | if err != nil { return nil, "", err } 33 | 34 | vtype := store.ValueType(vstr) 35 | var value store.Value 36 | switch vtype { 37 | case STRING_VALUE: 38 | value = &String{} 39 | case TOMBSTONE_VALUE: 40 | value = &Tombstone{} 41 | case BOOL_VALUE: 42 | value = &Boolean{} 43 | default: 44 | return nil, "", fmt.Errorf("Unexpected value type: %v", vtype) 45 | } 46 | 47 | if err := value.Deserialize(reader); err != nil { return nil, "", err} 48 | return value, vtype, nil 49 | } 50 | 51 | // ----------- equality helpers ----------- 52 | 53 | func baseValueEqual(v0, v1 store.Value) bool { 54 | if v0.GetValueType() != v1.GetValueType() { return false } 55 | if v0.GetTimestamp() != v1.GetTimestamp() { return false } 56 | return true 57 | } 58 | 59 | // ----------- reconcile helpers ----------- 60 | 61 | // returns the value with the highest timestamp 62 | func getHighValue(values []store.Value) store.Value { 63 | var highTimestamp time.Time 64 | var highValue store.Value 65 | for _, val := range values { 66 | if ts := val.GetTimestamp(); ts.After(highTimestamp) { 67 | highTimestamp = ts 68 | highValue = val 69 | } 70 | } 71 | return highValue 72 | } 73 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "cluster" 6 | "store" 7 | ) 8 | 9 | func main() { 10 | fmt.Printf("Hello world!") 11 | _ = cluster.Cluster{} 12 | _ = store.Store{} 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/message/errors.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | type MessageError struct { 4 | reason string 5 | } 6 | 7 | func (e *MessageError) Error() string { 8 | return e.reason 9 | } 10 | 11 | type MessageEncodingError struct { 12 | MessageError 13 | } 14 | 15 | func NewMessageEncodingError(reason string) *MessageEncodingError { 16 | return &MessageEncodingError{MessageError{reason}} 17 | } 18 | -------------------------------------------------------------------------------- /src/message/io.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | const ( 11 | MESSAGE_HEADER_SIZE = 4 12 | ) 13 | 14 | // function that returns an empty message 15 | type messageConstructor func() Message 16 | 17 | var constructors = make(map[uint32] messageConstructor) 18 | 19 | func RegisterMessage(mtype uint32, constructor messageConstructor) { 20 | constructors[mtype] = constructor 21 | } 22 | 23 | // writes a message to the given writer 24 | func WriteMessage(buf io.Writer, m Message) error { 25 | writer := bufio.NewWriter(buf) 26 | 27 | // write the message type 28 | mtype := m.GetType() 29 | if err:= binary.Write(writer, binary.LittleEndian, &mtype); err != nil { return err } 30 | if err := m.Serialize(writer); err != nil { return err } 31 | if err := writer.Flush(); err != nil { return err } 32 | return nil 33 | } 34 | 35 | // reads a message from the given reader 36 | func ReadMessage(buf io.Reader) (Message, error) { 37 | reader := bufio.NewReader(buf) 38 | var mtype uint32 39 | if err := binary.Read(reader, binary.LittleEndian, &mtype); err != nil { return nil, err } 40 | 41 | // iterate over the message types 42 | constructor, ok := constructors[mtype] 43 | if !ok { return nil, fmt.Errorf("Unknown message type: %v", mtype) } 44 | msg := constructor() 45 | if err := msg.Deserialize(reader); err != nil { return nil, err } 46 | return msg, nil 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "bufio" 5 | ) 6 | 7 | // message wire protocol is as follow: 8 | // [type (4b)][num fields (4b)]...[field size (4b)][field data] 9 | // each message type can define the data 10 | // format as needed 11 | type Message interface { 12 | 13 | // serializes the entire message, including 14 | // headers. Pass a wrapped connection in to send 15 | Serialize(*bufio.Writer) error 16 | 17 | // deserializes everything after the size data 18 | // pass in a wrapped exception to receive 19 | Deserialize(*bufio.Reader) error 20 | 21 | // returns the message type enum 22 | GetType() uint32 23 | 24 | // returns the number of bytes the 25 | // serialized message will contain 26 | NumBytes() int 27 | } 28 | -------------------------------------------------------------------------------- /src/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import ( 8 | "message" 9 | "store" 10 | "types" 11 | ) 12 | 13 | type NodeId struct { 14 | types.UUID 15 | } 16 | 17 | func NewNodeId() NodeId { 18 | return NodeId{types.NewUUID1()} 19 | } 20 | 21 | type NodeError struct { 22 | reason string 23 | } 24 | 25 | func NewNodeError(reason string) *NodeError { 26 | return &NodeError{reason:reason} 27 | } 28 | 29 | func (e *NodeError) Error() string { 30 | return e.reason 31 | } 32 | 33 | // the basic node interface 34 | type Node interface { 35 | GetId() NodeId 36 | 37 | ExecuteQuery(cmd string, key string, args []string, timestamp time.Time) (store.Value, error) 38 | SendMessage(message.Message) (message.Message, error) 39 | 40 | Start() error 41 | Stop() error 42 | IsStarted() bool 43 | } 44 | -------------------------------------------------------------------------------- /src/partitioner/md5.go: -------------------------------------------------------------------------------- 1 | package partitioner 2 | 3 | import ( 4 | "crypto/md5" 5 | ) 6 | 7 | // partitions on a keys md5 hash 8 | type MD5Partitioner struct { 9 | 10 | } 11 | 12 | func (p MD5Partitioner) GetToken(key string) Token { 13 | h := md5.New() 14 | h.Write([]byte(key)) 15 | return Token(h.Sum(nil)) 16 | } 17 | 18 | func NewMD5Partitioner() *MD5Partitioner { 19 | return &MD5Partitioner{} 20 | } 21 | -------------------------------------------------------------------------------- /src/partitioner/md5_test.go: -------------------------------------------------------------------------------- 1 | package partitioner 2 | 3 | import ( 4 | "launchpad.net/gocheck" 5 | ) 6 | 7 | type MD5PartitionerTest struct {} 8 | 9 | var _ = gocheck.Suite(&MD5PartitionerTest{}) 10 | 11 | func (t *MD5PartitionerTest) TestHashes(c *gocheck.C) { 12 | p := NewMD5Partitioner() 13 | c.Check( 14 | p.GetToken("1234"), 15 | gocheck.DeepEquals, 16 | Token([]byte{129, 220, 155, 219, 82, 208, 77, 194, 0, 54, 219, 216, 49, 62, 208, 85}), 17 | ) 18 | c.Check( 19 | p.GetToken("blake"), 20 | gocheck.DeepEquals, 21 | Token([]byte{58, 164, 158, 198, 191, 201, 16, 100, 127, 161, 197, 160, 19, 228, 142, 239}), 22 | ) 23 | c.Check( 24 | p.GetToken("abc"), 25 | gocheck.DeepEquals, 26 | Token([]byte{144, 1, 80, 152, 60, 210, 79, 176, 214, 150, 63, 125, 40, 225, 127, 114}), 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/partitioner/partitioner.go: -------------------------------------------------------------------------------- 1 | package partitioner 2 | 3 | type Token []byte 4 | 5 | type Partitioner interface { 6 | GetToken(key string) Token 7 | } 8 | -------------------------------------------------------------------------------- /src/partitioner/partitioner_test.go: -------------------------------------------------------------------------------- 1 | package partitioner 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | import ( 8 | "launchpad.net/gocheck" 9 | ) 10 | 11 | // Hook up gocheck into the "go test" runner. 12 | func Test(t *testing.T) { 13 | gocheck.TestingT(t) 14 | } 15 | -------------------------------------------------------------------------------- /src/redis/cmd_del.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func (s *Redis) validateDel(key string, args []string, timestamp time.Time) error { 9 | _ = key 10 | if len(args) != 0 { 11 | return fmt.Errorf("DEL takes 0 args, %v found", len(args)) 12 | } 13 | if timestamp.IsZero() { 14 | return fmt.Errorf("DEL Got zero timestamp") 15 | } 16 | return nil 17 | } 18 | 19 | // Removes the specified keys. A key is ignored if it does not exist. 20 | // Return value: Integer reply: The number of keys that were removed. 21 | // 22 | // internally, each key is deleted one at a time, and a bool value 23 | // is returned indicating if a key was deleted, and the previos value's 24 | // timestamp if one was found 25 | func (s *Redis) del(key string, ts time.Time) (*Boolean, error) { 26 | var rval *Boolean 27 | if val, exists := s.data[key]; exists { 28 | s.data[key] = NewTombstone(ts) 29 | rval = NewBoolean(true, val.GetTimestamp()) 30 | } else { 31 | rval = NewBoolean(false, time.Time{}) 32 | } 33 | return rval, nil 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/redis/cmd_del_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "store" 8 | "testing_helpers" 9 | ) 10 | 11 | // test that a tombstone value is written 12 | func TestDelExistingVal(t *testing.T) { 13 | r := setupRedis() 14 | 15 | // write value 16 | if _, err := r.ExecuteWrite("SET", "a", []string{"b"}, time.Now()); err != nil { 17 | t.Fatalf("Unexpected error setting 'a': %v", err) 18 | } 19 | 20 | // sanity check 21 | oldval, exists := r.data["a"] 22 | if ! exists { 23 | t.Errorf("No value found for 'a'") 24 | } 25 | expected, ok := oldval.(*String) 26 | if !ok { 27 | t.Errorf("actual value of unexpected type: %T", oldval) 28 | } 29 | 30 | // delete value 31 | ts := time.Now() 32 | rawval, err := r.ExecuteWrite("DEL", "a", []string{}, ts) 33 | if err != nil { 34 | t.Fatalf("Unexpected error deleting 'a': %v", err) 35 | } 36 | val, ok := rawval.(*Boolean) 37 | if !ok { 38 | t.Fatalf("Unexpected value type: %T", val) 39 | } 40 | 41 | testing_helpers.AssertEqual(t, "value", val.GetValue(), true) 42 | testing_helpers.AssertEqual(t, "time", val.GetTimestamp(), expected.GetTimestamp()) 43 | 44 | // check tombstone 45 | rawval, exists = r.data["a"] 46 | if !exists { 47 | t.Fatalf("Expected tombstone, got nil") 48 | } 49 | tsval, ok := rawval.(*Tombstone) 50 | if !ok { 51 | t.Errorf("tombstone value of unexpected type: %T", rawval) 52 | } 53 | testing_helpers.AssertEqual(t, "time", tsval.GetTimestamp(), ts) 54 | } 55 | 56 | func TestDelNonExistingVal(t *testing.T) { 57 | r := setupRedis() 58 | 59 | // sanity check 60 | _, exists := r.data["a"] 61 | if exists { 62 | t.Errorf("Value unexpectedly found for 'a'") 63 | } 64 | 65 | // delete value 66 | ts := time.Now() 67 | rawval, err := r.ExecuteWrite("DEL", "a", []string{}, ts) 68 | if err != nil { 69 | t.Fatalf("Unexpected error deleting 'a': %v", err) 70 | } 71 | val, ok := rawval.(*Boolean) 72 | if !ok { 73 | t.Fatalf("Unexpected value type: %T", val) 74 | } 75 | 76 | testing_helpers.AssertEqual(t, "value", val.GetValue(), false) 77 | testing_helpers.AssertEqual(t, "time", val.GetTimestamp(), time.Time{}) 78 | 79 | // check tombstone 80 | rawval, exists = r.data["a"] 81 | if exists { 82 | t.Fatalf("Unexpected tombstone val found: %T %v", rawval, rawval) 83 | } 84 | } 85 | 86 | // tests validation of DEL insructions 87 | func TestDelValidation(t *testing.T) { 88 | r := setupRedis() 89 | 90 | var val store.Value 91 | var err error 92 | 93 | val, err = r.ExecuteWrite("DEL", "a", []string{"x", "y"}, time.Now()) 94 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 95 | if err == nil { 96 | t.Errorf("Expected error, got nil") 97 | } else { 98 | t.Logf("Got expected err: %v", err) 99 | } 100 | 101 | val, err = r.ExecuteWrite("DEL", "a", []string{}, time.Time{}) 102 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 103 | if err == nil { 104 | t.Errorf("Expected error, got nil") 105 | } else { 106 | t.Logf("Got expected err: %v", err) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/redis/cmd_get.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "store" 5 | "fmt" 6 | ) 7 | 8 | func (s *Redis) validateGet(key string, args []string) error { 9 | _ = key 10 | if len(args) != 0 { 11 | return fmt.Errorf("too many args for GET") 12 | } 13 | return nil 14 | } 15 | 16 | // Get the value of key. If the key does not exist the special value nil is returned. 17 | // An error is returned if the value stored at key is not a string, because GET only handles string values. 18 | func (s *Redis) get(key string) (store.Value, error) { 19 | // TODO: check that the returned value is a string or tombstone value 20 | return s.data[key], nil 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/redis/cmd_get_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | "testing_helpers" 7 | ) 8 | 9 | func TestGet(t *testing.T) { 10 | r := setupRedis() 11 | expected := NewString("b", time.Now()) 12 | r.data["a"] = expected 13 | 14 | val, err := r.ExecuteRead("GET", "a", []string{}) 15 | if err != nil { 16 | t.Fatalf("Unexpected error on read: %v", err) 17 | } 18 | actual, ok := val.(*String) 19 | if !ok { 20 | t.Fatalf("Unexpected value type: %T", val) 21 | } 22 | 23 | testing_helpers.AssertEqual(t, "data", expected.GetValue(), actual.GetValue()) 24 | testing_helpers.AssertEqual(t, "time", expected.GetTimestamp(), actual.GetTimestamp()) 25 | } 26 | 27 | // tests that calling get on a key holding a value other than 28 | // a string value returns an error 29 | func TestGetNonStringFails(t *testing.T) { 30 | t.Skipf("other types not implemented yet") 31 | } 32 | 33 | // tests validation of GET insructions 34 | func TestGetValidation(t *testing.T) { 35 | r := setupRedis() 36 | 37 | // too many args 38 | val, err := r.ExecuteRead("GET", "a", []string{"b"}) 39 | if val != nil { 40 | t.Errorf("Unexpected non-nil value") 41 | } 42 | if err == nil { 43 | t.Errorf("Expected error, got nil") 44 | } else { 45 | t.Logf("Got expected err: %v", err) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/redis/cmd_set.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "store" 5 | "time" 6 | "fmt" 7 | ) 8 | 9 | func (s *Redis) validateSet(key string, args []string, timestamp time.Time) error { 10 | _ = key 11 | if len(args) != 1 { 12 | return fmt.Errorf("incorrect number of args for SET. Expected 1, got %v", len(args)) 13 | } 14 | if timestamp.IsZero() { 15 | return fmt.Errorf("DEL Got zero timestamp") 16 | } 17 | return nil 18 | } 19 | 20 | // Set key to hold the string value. If key already holds a value, it is overwritten, 21 | // regardless of its type. Any previous time to live associated with the key is discarded 22 | // on successful SET operation. 23 | func (s *Redis) set(key string, val string, ts time.Time) (store.Value) { 24 | existing, exists := s.data[key] 25 | if exists && ts.Before(existing.GetTimestamp()) { 26 | return existing 27 | } 28 | value := NewString(val, ts) 29 | s.data[key] = value 30 | return value 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/redis/cmd_set_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "testing_helpers" 6 | "time" 7 | 8 | "store" 9 | ) 10 | 11 | // tests basic function of set 12 | func TestSet(t *testing.T) { 13 | r := setupRedis() 14 | 15 | // sanity check 16 | _, exists := r.data["a"] 17 | if exists { 18 | t.Fatalf("Unexpectedly found 'a' in store") 19 | } 20 | 21 | ts := time.Now() 22 | rawval, err := r.ExecuteWrite("SET", "a", []string{"b"}, ts) 23 | if err != nil { 24 | t.Errorf("Unexpected write error: %v", err) 25 | } 26 | rval, ok := rawval.(*String) 27 | if !ok { 28 | t.Errorf("returned value of unexpected type: %T", rawval) 29 | } 30 | 31 | actualraw, exists := r.data["a"] 32 | if ! exists { 33 | t.Errorf("No value found for 'a'") 34 | } 35 | actual, ok := actualraw.(*String) 36 | if !ok { 37 | t.Errorf("actual value of unexpected type: %T", actualraw) 38 | } 39 | 40 | testing_helpers.AssertEqual(t, "rval data", "b", rval.GetValue()) 41 | testing_helpers.AssertEqual(t, "actual data", "b", actual.GetValue()) 42 | testing_helpers.AssertEqual(t, "rval time", ts, rval.GetTimestamp()) 43 | testing_helpers.AssertEqual(t, "actual time", ts, actual.GetTimestamp()) 44 | } 45 | 46 | // if set is called with a timestamp which is lower than 47 | // the existing value, it should be ignored 48 | func TestSetConflictingTimestamp(t *testing.T) { 49 | r := setupRedis() 50 | now := time.Now() 51 | then := now.Add(time.Duration(-1)) 52 | expected := r.set("a", "b", now) 53 | actual := r.set("a", "c", then) 54 | testing_helpers.AssertEqual(t, "set val", expected, actual) 55 | 56 | } 57 | 58 | // tests validation of SET insructions 59 | func TestSetValidation(t *testing.T) { 60 | r := setupRedis() 61 | 62 | var val store.Value 63 | var err error 64 | 65 | val, err = r.ExecuteWrite("SET", "a", []string{"x", "y"}, time.Now()) 66 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 67 | if err == nil { 68 | t.Errorf("Expected error, got nil") 69 | } else { 70 | t.Logf("Got expected err: %v", err) 71 | } 72 | 73 | val, err = r.ExecuteWrite("SET", "a", []string{"x"}, time.Time{}) 74 | if val != nil { t.Errorf("Expected nil value, got %v", val) } 75 | if err == nil { 76 | t.Errorf("Expected error, got nil") 77 | } else { 78 | t.Logf("Got expected err: %v", err) 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/redis/store.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | import ( 12 | "store" 13 | ) 14 | 15 | 16 | // read instructions 17 | const ( 18 | GET = "GET" 19 | ) 20 | 21 | // write instructions 22 | const ( 23 | SET = "SET" 24 | DEL = "DEL" 25 | ) 26 | 27 | 28 | type Redis struct { 29 | 30 | data map[string] store.Value 31 | 32 | // TODO: delete 33 | // temporary lock, used until 34 | // things are broken out into 35 | // goroutines 36 | lock sync.RWMutex 37 | 38 | } 39 | 40 | func NewRedis() *Redis { 41 | r := &Redis{ 42 | data:make(map[string] store.Value), 43 | } 44 | return r 45 | } 46 | 47 | func (s *Redis) SerializeValue(v store.Value) ([]byte, error) { 48 | buf := &bytes.Buffer{} 49 | if err := WriteValue(buf, v) ; err != nil { return nil, err } 50 | return buf.Bytes(), nil 51 | } 52 | 53 | func (s *Redis) DeserializeValue(b []byte) (store.Value, store.ValueType, error) { 54 | buf := bytes.NewBuffer(b) 55 | val, vtype, err := ReadValue(buf) 56 | if err != nil { return nil, "", err } 57 | return val, vtype, nil 58 | } 59 | 60 | func (s *Redis) Start() error { 61 | return nil 62 | } 63 | 64 | func (s *Redis) Stop() error { 65 | return nil 66 | } 67 | 68 | func (s *Redis) ExecuteRead(cmd string, key string, args []string) (store.Value, error) { 69 | s.lock.RLock() 70 | defer s.lock.RUnlock() 71 | switch cmd { 72 | case GET: 73 | // 74 | if err := s.validateGet(key, args); err != nil { return nil, err } 75 | rval, err := s.get(key) 76 | if err != nil { return nil, err } 77 | return rval, nil 78 | default: 79 | return nil, fmt.Errorf("Unrecognized read command: %v", cmd) 80 | } 81 | 82 | return nil, nil 83 | } 84 | 85 | func (s *Redis) ExecuteWrite(cmd string, key string, args []string, timestamp time.Time) (store.Value, error) { 86 | s.lock.Lock() 87 | defer s.lock.Unlock() 88 | 89 | switch cmd { 90 | case SET: 91 | if err := s.validateSet(key, args, timestamp); err != nil { return nil, err } 92 | return s.set(key, args[0], timestamp), nil 93 | case DEL: 94 | if err := s.validateDel(key, args, timestamp); err != nil { return nil, err } 95 | return s.del(key, timestamp) 96 | default: 97 | return nil, fmt.Errorf("Unrecognized write command: %v", cmd) 98 | } 99 | return nil, nil 100 | } 101 | 102 | // reconciles multiple values and returns instructions for correcting 103 | // the values on inaccurate nodes 104 | // 105 | // Reconcile should handle value maps with one value without hitting 106 | // a value type specific reconciliation function 107 | // 108 | // value type specific reconciliation functions should be able to handle 109 | // getting unfamiliar types, but can operate under the assumption that if 110 | // they're being called, the oldest timestamp of the given values belongs 111 | // to a value of it's type. 112 | func (s *Redis) Reconcile(key string, values map[string] store.Value) (store.Value, map[string][]*store.Instruction, error) { 113 | switch len(values){ 114 | case 0: 115 | return nil, nil, fmt.Errorf("At least one value must be provided") 116 | case 1: 117 | var val store.Value 118 | for _, v := range values { val = v } 119 | return val, nil, nil 120 | default: 121 | highValue := getHighValue(values) 122 | 123 | switch highValue.GetValueType() { 124 | case STRING_VALUE: 125 | return reconcileString(key, highValue.(*String), values) 126 | case TOMBSTONE_VALUE: 127 | return reconcileTombstone(key, highValue.(*Tombstone), values) 128 | default: 129 | return nil, make(map[string][]*store.Instruction), fmt.Errorf("Unknown value type: %T", highValue) 130 | } 131 | } 132 | return nil, make(map[string][]*store.Instruction), nil 133 | } 134 | 135 | func (s *Redis) IsReadCommand(cmd string) bool { 136 | switch strings.ToUpper(cmd) { 137 | case GET: 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | func (s *Redis) IsWriteCommand(cmd string) bool { 144 | switch strings.ToUpper(cmd) { 145 | case SET, DEL: 146 | return true 147 | } 148 | return false 149 | } 150 | 151 | func (s *Redis) ReturnsValue(cmd string) bool { 152 | switch strings.ToUpper(cmd) { 153 | case GET: 154 | return true 155 | } 156 | return false 157 | } 158 | 159 | // ----------- data import / export ----------- 160 | 161 | 162 | // blindly gets the contents of the given key 163 | func (s *Redis) GetRawKey(key string) (store.Value, error) { 164 | val, ok := s.data[key] 165 | if !ok { 166 | return nil, fmt.Errorf("key [%v] does not exist", key) 167 | } 168 | return val, nil 169 | } 170 | 171 | // blindly sets the contents of the given key 172 | func (s *Redis) SetRawKey(key string, val store.Value) error { 173 | s.data[key] = val 174 | return nil 175 | } 176 | 177 | // returns all of the keys held by the store, including keys containing 178 | // tombstones 179 | func (s *Redis) GetKeys() []string { 180 | var i int 181 | keys := make([]string, len(s.data)) 182 | for key, _ := range s.data { 183 | keys[i] = key 184 | i ++ 185 | } 186 | return keys 187 | } 188 | 189 | func (s *Redis) KeyExists(key string) bool { 190 | _, ok := s.data[key] 191 | return ok 192 | } 193 | -------------------------------------------------------------------------------- /src/redis/store_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | "store" 9 | ) 10 | 11 | // table of instructions, and whether they're 12 | // a write (true) or read (false) 13 | var isWrite = []struct { 14 | cmd string 15 | result bool 16 | }{ 17 | {"GET", false}, 18 | {"SET", true}, 19 | {"DEL", true}, 20 | } 21 | 22 | func TestIsWriteCmd(t *testing.T) { 23 | r := &Redis{} 24 | for _, c := range isWrite { 25 | if result := r.IsWriteCommand(c.cmd); result != c.result { 26 | if result { 27 | t.Errorf("%v erroneously identified as a write", c.cmd) 28 | } else { 29 | t.Errorf("%v not identified as a write", c.cmd) 30 | } 31 | } 32 | } 33 | } 34 | 35 | func TestIsReadCmd(t *testing.T) { 36 | r := &Redis{} 37 | for _, c := range isWrite { 38 | if result := r.IsReadCommand(c.cmd); result != !c.result { 39 | if result { 40 | t.Errorf("%v erroneously identified as a read", c.cmd) 41 | } else { 42 | t.Errorf("%v not identified as a read", c.cmd) 43 | } 44 | } 45 | } 46 | } 47 | 48 | func TestInterfaceIsImplemented(t *testing.T) { 49 | t.Skipf("Not working yet!") 50 | func(s store.Store) { _ = s }(&Redis{}) 51 | } 52 | 53 | // ----------- data import / export ----------- 54 | 55 | func TestGetRawKeySuccess(t *testing.T) { 56 | r := setupRedis() 57 | expected, err := r.ExecuteWrite("SET", "a", []string{"b"}, time.Now()) 58 | if err != nil { 59 | t.Fatalf("Unexpected error executing set: %v", err) 60 | } 61 | 62 | rval, err := r.GetRawKey("a") 63 | if err != nil { 64 | t.Fatal("unexpectedly got error: %v", err) 65 | } 66 | val, ok := rval.(*String) 67 | if !ok { 68 | t.Fatal("expected value of type stringValue, got %T", rval) 69 | } 70 | testing_helpers.AssertEqual(t, "value", expected, val) 71 | } 72 | 73 | func TestSetRawKey(t *testing.T) { 74 | r := setupRedis() 75 | expected := NewBoolean(true, time.Now()) 76 | r.SetRawKey("x", expected) 77 | 78 | rval, exists := r.data["x"] 79 | if !exists { 80 | t.Fatalf("no value found for key 'x'") 81 | } 82 | val, ok := rval.(*Boolean) 83 | if !ok { 84 | t.Fatal("expected value of type boolValues, got %T", rval) 85 | } 86 | 87 | testing_helpers.AssertEqual(t, "val", expected, val) 88 | } 89 | 90 | func TestGetKeys(t *testing.T) { 91 | r := setupRedis() 92 | val := NewBoolean(true, time.Now()) 93 | r.SetRawKey("x", val) 94 | r.SetRawKey("y", val) 95 | r.SetRawKey("z", val) 96 | 97 | // make set of expected keys 98 | expected := map[string]bool {"x": true, "y": true, "z": true} 99 | 100 | seen := make(map[string] bool) 101 | keys := r.GetKeys() 102 | testing_helpers.AssertEqual(t, "num keys", len(expected), len(keys)) 103 | for _, key := range keys { 104 | if expected[key] && !seen[key] { 105 | seen[key] = true 106 | } else if seen[key] { 107 | t.Errorf("Key '%v' seen more than once", key) 108 | } else { 109 | t.Errorf("Unexpected key returned: '%v'", key) 110 | } 111 | } 112 | } 113 | 114 | func TestKeyExists(t *testing.T) { 115 | r := setupRedis() 116 | val := NewBoolean(true, time.Now()) 117 | r.SetRawKey("x", val) 118 | 119 | testing_helpers.AssertEqual(t, "existing key", true, r.KeyExists("x")) 120 | testing_helpers.AssertEqual(t, "existing key", false, r.KeyExists("y")) 121 | } 122 | 123 | // tests that attempting to reconcile an empty map 124 | // returns an error 125 | func TestReconcilingEmptyMap(t *testing.T) { 126 | vmap := map[string]store.Value {} 127 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 128 | 129 | if err == nil { 130 | t.Fatalf("expected reconciliation error") 131 | } else { 132 | t.Log(err) 133 | } 134 | testing_helpers.AssertEqual(t, "returned val", nil, ractual) 135 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 136 | } 137 | 138 | // tests reconciling a value map with only a single element 139 | func TestReconcileSingleValue(t *testing.T) { 140 | ts0 := time.Now() 141 | expected := NewString("a", ts0) 142 | vmap := map[string]store.Value { "0": expected } 143 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 144 | 145 | if err != nil { 146 | t.Fatalf("unexpected reconciliation error: %v", err) 147 | } 148 | 149 | actual, ok := ractual.(*String) 150 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 151 | 152 | assertEqualValue(t, "reconciled value", expected, actual) 153 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/redis/testing.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "store" 6 | ) 7 | 8 | // returns a redis instance with defaults set 9 | func setupRedis() *Redis { 10 | return NewRedis() 11 | } 12 | 13 | func assertEqualValue(t *testing.T, name string, v1 store.Value, v2 store.Value) { 14 | if !v1.Equal(v2) { 15 | t.Errorf("\x1b[1m\x1b[35m%v mismatch. Expecting [%v], got [%v]\x1b[0m", name, v1, v2) 16 | } else { 17 | t.Logf("%v OK: [%v]", name, v1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/redis/val_bool.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "serializer" 8 | "store" 9 | ) 10 | 11 | type Boolean struct { 12 | value bool 13 | time time.Time 14 | } 15 | 16 | func NewBoolean(val bool, timestamp time.Time) *Boolean { 17 | v := &Boolean{ 18 | value:val, 19 | time:timestamp, 20 | } 21 | return v 22 | } 23 | 24 | func (v *Boolean) GetValue() bool { 25 | return v.value 26 | } 27 | 28 | func (v *Boolean) GetTimestamp() time.Time { 29 | return v.time 30 | } 31 | 32 | func (v *Boolean) GetValueType() store.ValueType { 33 | return BOOL_VALUE 34 | } 35 | 36 | func (v *Boolean) Equal(o store.Value) bool { 37 | if !baseValueEqual(v, o) { return false } 38 | other := o.(*Boolean) 39 | if v.value != other.value { return false } 40 | return true 41 | } 42 | 43 | func (v *Boolean) Serialize(buf *bufio.Writer) error { 44 | var b byte 45 | if v.value { b = 0xff } 46 | if err := buf.WriteByte(b); err != nil { 47 | return err 48 | } 49 | if err := serializer.WriteTime(buf, v.time); err != nil { 50 | return err 51 | } 52 | if err := buf.Flush(); err != nil { 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | func (v *Boolean) Deserialize(buf *bufio.Reader) error { 59 | if b, err := buf.ReadByte(); err != nil { 60 | return err 61 | } else { 62 | v.value = b != 0x00 63 | } 64 | if t, err := serializer.ReadTime(buf); err != nil { 65 | return err 66 | } else { 67 | v.time = t 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /src/redis/val_bool_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | ) 9 | 10 | func TestBooleanValue(t *testing.T) { 11 | s := setupRedis() 12 | for _, val := range []bool{true, false} { 13 | src := NewBoolean(val, time.Now()) 14 | 15 | b, err := s.SerializeValue(src) 16 | if err != nil { 17 | t.Fatalf("Unexpected serialization error: %v", err) 18 | } 19 | 20 | val, vtype, err := s.DeserializeValue(b) 21 | if err != nil { 22 | t.Fatalf("Unexpected deserialization error: %v", err) 23 | } 24 | if vtype != BOOL_VALUE { 25 | t.Fatalf("Unexpected value type enum: %v", vtype) 26 | } 27 | dst, ok := val.(*Boolean) 28 | if !ok { 29 | t.Fatalf("Unexpected value type: %T", val) 30 | } 31 | 32 | testing_helpers.AssertEqual(t, "value", src.value, dst.value) 33 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 34 | } 35 | } 36 | 37 | // tests the boolean value's equality method 38 | func TestBooleanEquality(t *testing.T) { 39 | t0 := time.Now() 40 | v0 := NewBoolean(true, t0) 41 | 42 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewBoolean(true, t0))) 43 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewBoolean(true, t0.Add(4)))) 44 | testing_helpers.AssertEqual(t, "unequal value", false, v0.Equal(NewBoolean(false, t0))) 45 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewString("asdf", t0))) 46 | } 47 | -------------------------------------------------------------------------------- /src/redis/val_string.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "store" 8 | "serializer" 9 | ) 10 | 11 | // a single value used for 12 | // key/val types 13 | type String struct { 14 | value string 15 | time time.Time 16 | } 17 | 18 | // single value constructor 19 | func NewString(value string, time time.Time) *String { 20 | v := &String{ 21 | value:value, 22 | time:time, 23 | } 24 | return v 25 | } 26 | 27 | func (v *String) GetValue() string { 28 | return v.value 29 | } 30 | 31 | func (v *String) GetTimestamp() time.Time { 32 | return v.time 33 | } 34 | 35 | func (v *String) GetValueType() store.ValueType { 36 | return STRING_VALUE 37 | } 38 | 39 | func (v *String) Equal(o store.Value) bool { 40 | if !baseValueEqual(v, o) { return false } 41 | other := o.(*String) 42 | if v.value != other.value { return false } 43 | return true 44 | } 45 | 46 | func (v *String) Serialize(buf *bufio.Writer) error { 47 | if err := serializer.WriteFieldBytes(buf, []byte(v.value)); err != nil { 48 | return err 49 | } 50 | if err := serializer.WriteTime(buf, v.time); err != nil { 51 | return err 52 | } 53 | if err := buf.Flush(); err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | 59 | func (v *String) Deserialize(buf *bufio.Reader) error { 60 | if val, err := serializer.ReadFieldBytes(buf); err != nil { 61 | return err 62 | } else { 63 | v.value = string(val) 64 | } 65 | 66 | if t, err := serializer.ReadTime(buf); err != nil { 67 | return err 68 | } else { 69 | v.time = t 70 | } 71 | return nil 72 | } 73 | 74 | func reconcileString(key string, highValue *String, values map[string]store.Value) (*String, map[string][]*store.Instruction, error) { 75 | // create instructions for the unequal nodes 76 | instructions := make(map[string][]*store.Instruction) 77 | for nodeid, val := range values { 78 | if !highValue.Equal(val) { 79 | instructions[nodeid] = []*store.Instruction{&store.Instruction{ 80 | Cmd:"SET", 81 | Key:key, 82 | Args:[]string{highValue.value}, 83 | Timestamp:highValue.time, 84 | }} 85 | } 86 | } 87 | 88 | return highValue, instructions, nil 89 | } 90 | -------------------------------------------------------------------------------- /src/redis/val_string_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "store" 8 | "testing_helpers" 9 | ) 10 | 11 | // tests the string value 12 | func TestStringValue(t *testing.T) { 13 | s := setupRedis() 14 | src := NewString("blake", time.Now()) 15 | 16 | b, err := s.SerializeValue(src) 17 | if err != nil { 18 | t.Fatalf("Unexpected serialization error: %v", err) 19 | } 20 | 21 | val, vtype, err := s.DeserializeValue(b) 22 | if err != nil { 23 | t.Fatalf("Unexpected deserialization error: %v", err) 24 | } 25 | if vtype != STRING_VALUE { 26 | t.Fatalf("Unexpected value type enum: %v", vtype) 27 | } 28 | dst, ok := val.(*String) 29 | if !ok { 30 | t.Fatalf("Unexpected value type: %T", val) 31 | } 32 | 33 | testing_helpers.AssertEqual(t, "value", src.value, dst.value) 34 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 35 | } 36 | 37 | // tests that the tombstone struct satisfies the 38 | // value interface 39 | func TestStringInterface(_ *testing.T) { 40 | func (store.Value){}(NewTombstone(time.Now())) 41 | } 42 | 43 | // tests that mismatched values are reconciled and 44 | // corrected as expected 45 | func TestStringMismatchReconciliation(t *testing.T) { 46 | ts0 := time.Now() 47 | ts1 := ts0.Add(time.Duration(-3000)) 48 | expected := NewString("a", ts0) 49 | vmap := map[string]store.Value { 50 | "0": expected, 51 | "1": NewString("b", ts1), 52 | "2": expected, 53 | } 54 | 55 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 56 | 57 | if err != nil { 58 | t.Fatalf("unexpected reconciliation error: %v", err) 59 | } 60 | 61 | actual, ok := ractual.(*String) 62 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 63 | 64 | assertEqualValue(t, "reconciled value", expected, actual) 65 | testing_helpers.AssertEqual(t, "adjustment size", 1, len(adjustments)) 66 | 67 | instructions, ok := adjustments["1"] 68 | if !ok { 69 | t.Fatalf("instruction set for '1' not found") 70 | } 71 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 72 | 73 | instruction := instructions[0] 74 | expected_instr := store.Instruction{Cmd:"SET", Key:"k", Args:[]string{"a"}, Timestamp:ts0} 75 | if !expected_instr.Equal(instruction) { 76 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 77 | } 78 | } 79 | 80 | // should set values of different types to the value 81 | // with the largest timestamp 82 | func TestStringMultiTypeReconciliation(t *testing.T) { 83 | ts0 := time.Now() 84 | ts1 := ts0.Add(time.Duration(-3000)) 85 | expected := NewString("a", ts0) 86 | vmap := map[string]store.Value { 87 | "0": expected, 88 | "1": NewTombstone(ts1), 89 | } 90 | 91 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 92 | 93 | if err != nil { 94 | t.Fatalf("unexpected reconciliation error: %v", err) 95 | } 96 | 97 | actual, ok := ractual.(*String) 98 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 99 | 100 | assertEqualValue(t, "reconciled value", expected, actual) 101 | testing_helpers.AssertEqual(t, "adjustment size", 1, len(adjustments)) 102 | 103 | instructions, ok := adjustments["1"] 104 | if !ok { 105 | t.Fatalf("instruction set for '1' not found") 106 | } 107 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 108 | 109 | instruction := instructions[0] 110 | expected_instr := store.Instruction{Cmd:"SET", Key:"k", Args:[]string{"a"}, Timestamp:ts0} 111 | if !expected_instr.Equal(instruction) { 112 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 113 | } 114 | } 115 | 116 | // should return the correct value and no adjustment 117 | // instructions if all of the values match 118 | func TestStringNoOpReconciliation(t *testing.T) { 119 | ts0 := time.Now() 120 | expected := NewString("a", ts0) 121 | vmap := map[string]store.Value { 122 | "0": expected, 123 | "1": expected, 124 | "2": expected, 125 | } 126 | 127 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 128 | 129 | if err != nil { 130 | t.Fatalf("unexpected reconciliation error: %v", err) 131 | } 132 | 133 | actual, ok := ractual.(*String) 134 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 135 | 136 | assertEqualValue(t, "reconciled value", expected, actual) 137 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 138 | } 139 | 140 | // tests the boolean value's equality method 141 | func TestStringEquality(t *testing.T) { 142 | t0 := time.Now() 143 | v0 := NewString("abc", t0) 144 | 145 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewString("abc", t0))) 146 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewString("abc", t0.Add(4)))) 147 | testing_helpers.AssertEqual(t, "unequal value", false, v0.Equal(NewString("def", t0))) 148 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewTombstone(t0))) 149 | } 150 | -------------------------------------------------------------------------------- /src/redis/val_tombstone.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "time" 6 | 7 | "serializer" 8 | "store" 9 | ) 10 | 11 | // a value indicating a deletion 12 | type Tombstone struct { 13 | time time.Time 14 | } 15 | 16 | // single value constructor 17 | func NewTombstone(time time.Time) *Tombstone { 18 | v := &Tombstone{ 19 | time:time, 20 | } 21 | return v 22 | } 23 | 24 | func (v *Tombstone) GetTimestamp() time.Time { 25 | return v.time 26 | } 27 | 28 | func (v *Tombstone) GetValueType() store.ValueType { 29 | return TOMBSTONE_VALUE 30 | } 31 | 32 | func (v *Tombstone) Equal(o store.Value) bool { 33 | return baseValueEqual(v, o) 34 | } 35 | 36 | func (v *Tombstone) Serialize(buf *bufio.Writer) error { 37 | if err := serializer.WriteTime(buf, v.time); err != nil { 38 | return err 39 | } 40 | if err := buf.Flush(); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (v *Tombstone) Deserialize(buf *bufio.Reader) error { 47 | if t, err := serializer.ReadTime(buf); err != nil { 48 | return err 49 | } else { 50 | v.time = t 51 | } 52 | return nil 53 | } 54 | 55 | func reconcileTombstone(key string, highValue *Tombstone, values map[string]store.Value) (*Tombstone, map[string][]*store.Instruction, error) { 56 | // create instructions for the unequal nodes 57 | instructions := make(map[string][]*store.Instruction) 58 | for nodeid, val := range values { 59 | if val != highValue { 60 | instructions[nodeid] = []*store.Instruction{&store.Instruction{ 61 | Cmd:"DEL", 62 | Key:key, 63 | Args:[]string{}, 64 | Timestamp:highValue.time, 65 | }} 66 | } 67 | } 68 | 69 | return highValue, instructions, nil 70 | } 71 | -------------------------------------------------------------------------------- /src/redis/val_tombstone_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "testing_helpers" 8 | "store" 9 | ) 10 | 11 | func TestTombstoneValue(t *testing.T) { 12 | s := setupRedis() 13 | src := NewTombstone(time.Now()) 14 | 15 | b, err := s.SerializeValue(src) 16 | if err != nil { 17 | t.Fatalf("Unexpected serialization error: %v", err) 18 | } 19 | 20 | val, vtype, err := s.DeserializeValue(b) 21 | if err != nil { 22 | t.Fatalf("Unexpected deserialization error: %v", err) 23 | } 24 | if vtype != TOMBSTONE_VALUE { 25 | t.Fatalf("Unexpected value type enum: %v", vtype) 26 | } 27 | dst, ok := val.(*Tombstone) 28 | if !ok { 29 | t.Fatalf("Unexpected value type: %T", val) 30 | } 31 | 32 | testing_helpers.AssertEqual(t, "time", src.time, dst.time) 33 | } 34 | 35 | // tests that the tombstone struct satisfies the 36 | // value interface 37 | func TestTombstoneInterface(t *testing.T) { 38 | func (store.Value){}(NewTombstone(time.Now())) 39 | } 40 | 41 | // tests that mismatched values are reconciled and 42 | // corrected as expected 43 | func TestTombstoneMismatchReconciliation(t *testing.T) { 44 | ts0 := time.Now() 45 | ts1 := ts0.Add(time.Duration(-3000)) 46 | expected := NewTombstone(ts0) 47 | vmap := map[string]store.Value { 48 | "0": expected, 49 | "1": NewTombstone(ts1), 50 | "2": expected, 51 | } 52 | 53 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 54 | 55 | if err != nil { 56 | t.Fatalf("unexpected reconciliation error: %v", err) 57 | } 58 | 59 | actual, ok := ractual.(*Tombstone) 60 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 61 | 62 | assertEqualValue(t, "reconciled value", expected, actual) 63 | testing_helpers.AssertEqual(t, "adjustment size", 1, len(adjustments)) 64 | 65 | instructions, ok := adjustments["1"] 66 | if !ok { 67 | t.Fatalf("instruction set for '1' not found") 68 | } 69 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 70 | 71 | instruction := instructions[0] 72 | expected_instr := store.Instruction{Cmd:"DEL", Key:"k", Args:[]string{}, Timestamp:ts0} 73 | if !expected_instr.Equal(instruction) { 74 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 75 | } 76 | } 77 | 78 | // should set values of different types to the value 79 | // with the largest timestamp 80 | func TestTombstoneMultiTypeReconciliation(t *testing.T) { 81 | ts0 := time.Now() 82 | ts1 := ts0.Add(time.Duration(-3000)) 83 | expected := NewTombstone(ts0) 84 | vmap := map[string]store.Value { 85 | "0": expected, 86 | "1": NewString("a", ts1), 87 | } 88 | 89 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 90 | 91 | if err != nil { 92 | t.Fatalf("unexpected reconciliation error: %v", err) 93 | } 94 | 95 | actual, ok := ractual.(*Tombstone) 96 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 97 | 98 | assertEqualValue(t, "reconciled value", expected, actual) 99 | testing_helpers.AssertEqual(t, "adjustment size", 1, len(adjustments)) 100 | 101 | instructions, ok := adjustments["1"] 102 | if !ok { 103 | t.Fatalf("instruction set for '1' not found") 104 | } 105 | testing_helpers.AssertEqual(t, "num instructions", 1, len(instructions)) 106 | 107 | instruction := instructions[0] 108 | expected_instr := store.Instruction{Cmd:"DEL", Key:"k", Args:[]string{}, Timestamp:ts0} 109 | if !expected_instr.Equal(instruction) { 110 | t.Fatalf("unexpected instruction value. Expected: [%v], got: [%v]", expected_instr, instruction) 111 | } 112 | } 113 | 114 | // should return the correct value and no adjustment 115 | // instructions if all of the values match 116 | func TestTombstoneNoOpReconciliation(t *testing.T) { 117 | ts0 := time.Now() 118 | expected := NewTombstone(ts0) 119 | vmap := map[string]store.Value { 120 | "0": expected, 121 | "1": expected, 122 | "2": expected, 123 | } 124 | 125 | ractual, adjustments, err := setupRedis().Reconcile("k", vmap) 126 | 127 | if err != nil { 128 | t.Fatalf("unexpected reconciliation error: %v", err) 129 | } 130 | 131 | actual, ok := ractual.(*Tombstone) 132 | if !ok { t.Fatalf("Unexpected return value type: %T", ractual) } 133 | 134 | assertEqualValue(t, "reconciled value", expected, actual) 135 | testing_helpers.AssertEqual(t, "adjustment size", 0, len(adjustments)) 136 | } 137 | 138 | // tests the tombstone value's equality method 139 | func TestTombstoneEquality(t *testing.T) { 140 | t0 := time.Now() 141 | v0 := NewTombstone(t0) 142 | 143 | testing_helpers.AssertEqual(t, "equal value", true, v0.Equal(NewTombstone(t0))) 144 | testing_helpers.AssertEqual(t, "unequal timestamp", false, v0.Equal(NewTombstone(t0.Add(4)))) 145 | testing_helpers.AssertEqual(t, "unequal type", false, v0.Equal(NewString("asdf", t0))) 146 | } 147 | -------------------------------------------------------------------------------- /src/redis/values.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "serializer" 10 | "store" 11 | ) 12 | 13 | const ( 14 | STRING_VALUE = store.ValueType("STRING") 15 | TOMBSTONE_VALUE = store.ValueType("TOMBSTONE") 16 | BOOL_VALUE = store.ValueType("BOOL") 17 | ) 18 | 19 | func WriteValue(buf io.Writer, v store.Value) error { 20 | writer := bufio.NewWriter(buf) 21 | 22 | vtype := v.GetValueType() 23 | if err := serializer.WriteFieldBytes(writer, []byte(vtype)); err != nil { return err } 24 | if err := v.Serialize(writer); err != nil { return err } 25 | if err := writer.Flush(); err != nil { return err } 26 | return nil 27 | } 28 | 29 | func ReadValue(buf io.Reader) (store.Value, store.ValueType, error) { 30 | reader := bufio.NewReader(buf) 31 | vstr, err := serializer.ReadFieldBytes(reader) 32 | if err != nil { return nil, "", err } 33 | 34 | vtype := store.ValueType(vstr) 35 | var value store.Value 36 | switch vtype { 37 | case STRING_VALUE: 38 | value = &String{} 39 | case TOMBSTONE_VALUE: 40 | value = &Tombstone{} 41 | case BOOL_VALUE: 42 | value = &Boolean{} 43 | default: 44 | return nil, "", fmt.Errorf("Unexpected value type: %v", vtype) 45 | } 46 | 47 | if err := value.Deserialize(reader); err != nil { return nil, "", err} 48 | return value, vtype, nil 49 | } 50 | 51 | // ----------- equality helpers ----------- 52 | 53 | func baseValueEqual(v0, v1 store.Value) bool { 54 | if v0.GetValueType() != v1.GetValueType() { return false } 55 | if v0.GetTimestamp() != v1.GetTimestamp() { return false } 56 | return true 57 | } 58 | 59 | // ----------- reconcile helpers ----------- 60 | 61 | // returns the value with the highest timestamp 62 | func getHighValue(values map[string]store.Value) store.Value { 63 | var highTimestamp time.Time 64 | var highValue store.Value 65 | for _, val := range values { 66 | if ts := val.GetTimestamp(); ts.After(highTimestamp) { 67 | highTimestamp = ts 68 | highValue = val 69 | } 70 | } 71 | return highValue 72 | } 73 | -------------------------------------------------------------------------------- /src/serializer/serializer.go: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | common serialize/deserialize functions 4 | 5 | */ 6 | package serializer 7 | 8 | import ( 9 | "encoding/binary" 10 | "fmt" 11 | "bufio" 12 | "time" 13 | ) 14 | 15 | // writes the field length, then the field to the writer 16 | func WriteFieldBytes(buf *bufio.Writer, bytes []byte) error { 17 | //write field length 18 | size := uint32(len(bytes)) 19 | if err := binary.Write(buf, binary.LittleEndian, &size); err != nil { 20 | return err 21 | } 22 | // write field 23 | n, err := buf.Write(bytes); 24 | if err != nil { 25 | return err 26 | } 27 | if uint32(n) != size { 28 | return fmt.Errorf("unexpected num bytes written. Expected %v, got %v", size, n) 29 | } 30 | return nil 31 | } 32 | 33 | // reads the specified number of bytes out 34 | // of the reader, performing multiple reads 35 | // if neccesary 36 | func ReadBytes(buf *bufio.Reader, size int) ([]byte, error) { 37 | numRead := 0 38 | target := make([]byte, size) 39 | for numRead < size { 40 | n, err := buf.Read(target[numRead:]) 41 | if err != nil { 42 | return nil, err 43 | } 44 | numRead += n 45 | } 46 | return target, nil 47 | } 48 | 49 | // read field bytes 50 | func ReadFieldBytes(buf *bufio.Reader) ([]byte, error) { 51 | var size uint32 52 | if err := binary.Read(buf, binary.LittleEndian, &size); err != nil { 53 | return nil, err 54 | } 55 | 56 | bytesRead, err := ReadBytes(buf, int(size)) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | if n := len(bytesRead); n != int(size) { 62 | return nil, fmt.Errorf("unexpected num bytes read. Expected %v, got %v", size, n) 63 | } 64 | return bytesRead, nil 65 | } 66 | 67 | func WriteFieldString(buf *bufio.Writer, str string) error { 68 | return WriteFieldBytes(buf, []byte(str)) 69 | } 70 | 71 | func ReadFieldString(buf *bufio.Reader) (string, error) { 72 | bytes, err := ReadFieldBytes(buf) 73 | return string(bytes), err 74 | } 75 | 76 | // writes a time value 77 | func WriteTime(buf *bufio.Writer, t time.Time) error { 78 | b, err := t.GobEncode() 79 | if err != nil { return err } 80 | if err := WriteFieldBytes(buf, b); err != nil { 81 | return err 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // reads a time value 88 | func ReadTime(buf *bufio.Reader) (time.Time, error) { 89 | t := time.Time{} 90 | timeBytes, err := ReadFieldBytes(buf) 91 | if err != nil { 92 | return t, err 93 | } 94 | if err := t.GobDecode(timeBytes); err != nil { 95 | return t, err 96 | } 97 | return t, nil 98 | } 99 | 100 | func NumStringBytes(s string) int { 101 | return 4 + len(s) 102 | } 103 | 104 | func NumSliceBytes(b []byte) int { 105 | return 4 + len(b) 106 | } 107 | 108 | func NumTimeBytes() int { 109 | // header + bytes 110 | return 4 + 15 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "cluster" 5 | "store" 6 | ) 7 | 8 | 9 | type Server struct { 10 | cluster *cluster.Cluster 11 | store *store.Store 12 | } 13 | -------------------------------------------------------------------------------- /src/store/instruction.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "serializer" 11 | ) 12 | 13 | // an instruction to be executed against 14 | // the store. These objects should be 15 | // considered immutable once instantiated 16 | type Instruction struct { 17 | Cmd string 18 | Key string 19 | Args []string 20 | Timestamp time.Time 21 | } 22 | 23 | // creates a new instruction 24 | func NewInstruction(cmd string, key string, args []string, timestamp time.Time) Instruction { 25 | return Instruction{ 26 | Cmd: cmd, 27 | Key: key, 28 | Args: args, 29 | Timestamp: timestamp, 30 | } 31 | } 32 | 33 | // instruction equality test 34 | func (i *Instruction) Equal(o Instruction) bool { 35 | if i.Cmd != o.Cmd { return false } 36 | if i.Key != o.Key { return false } 37 | if len(i.Args) != len(o.Args) { return false } 38 | for n:=0;n replica nodes 83 | func (dc *DatacenterContainer) GetNodesForToken(t partitioner.Token, replicationFactor uint32) map[DatacenterID][]Node { 84 | dc.lock.RLock() 85 | defer dc.lock.RUnlock() 86 | 87 | // allocate an additional space for the local node when this is used in queries 88 | nodes := make(map[DatacenterID][]Node, len(dc.rings) + 1) 89 | for dcid, ring := range dc.rings { 90 | nodes[dcid] = ring.GetNodesForToken(t, replicationFactor) 91 | } 92 | 93 | return nodes 94 | } 95 | -------------------------------------------------------------------------------- /src/topology/datacenter_test.go: -------------------------------------------------------------------------------- 1 | package topology 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | import ( 8 | "launchpad.net/gocheck" 9 | ) 10 | 11 | import ( 12 | "node" 13 | "partitioner" 14 | ) 15 | 16 | func setupDC(numDCs int, numNodes int) *DatacenterContainer { 17 | dc := NewDatacenterContainer() 18 | 19 | for i:=0; i replica nodes 124 | func (t *Topology) GetNodesForToken(tk partitioner.Token) map[DatacenterID][]Node { 125 | t.lock.RLock() 126 | defer t.lock.RUnlock() 127 | 128 | // allocate an additional space for the local node when this is used in queries 129 | nodes := make(map[DatacenterID][]Node, len(t.rings)+1) 130 | for dcid, ring := range t.rings { 131 | nodes[dcid] = ring.GetNodesForToken(tk, uint32(t.replicationFactor)) 132 | } 133 | 134 | return nodes 135 | } 136 | 137 | // returns local dc replicas for the given token 138 | func (t *Topology) GetLocalNodesForToken(tk partitioner.Token) []Node { 139 | t.lock.RLock() 140 | defer t.lock.RUnlock() 141 | 142 | ring := t.rings[t.localDcID] 143 | if ring == nil { 144 | return []Node{} 145 | } 146 | return ring.GetNodesForToken(tk, uint32(t.replicationFactor)) 147 | } 148 | 149 | // returns true if the given token is replicated by the local node 150 | func (t *Topology) TokenLocallyReplicated(tk partitioner.Token) bool { 151 | for _, n := range t.GetLocalNodesForToken(tk) { 152 | if n.GetId() == t.localNodeID { 153 | return true 154 | } 155 | } 156 | return false 157 | } 158 | -------------------------------------------------------------------------------- /src/topology/topology_test.go: -------------------------------------------------------------------------------- 1 | package topology 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | import ( 9 | "launchpad.net/gocheck" 10 | ) 11 | 12 | import ( 13 | "node" 14 | "partitioner" 15 | ) 16 | 17 | // Hook up gocheck into the "go test" runner. 18 | func Test(t *testing.T) { 19 | gocheck.TestingT(t) 20 | } 21 | 22 | type TopologyTest struct { 23 | tp *Topology 24 | localNID node.NodeId 25 | localDCID DatacenterID 26 | numDCs int 27 | numNodes int 28 | replicationFactor int 29 | } 30 | 31 | var _ = gocheck.Suite(&TopologyTest{}) 32 | 33 | func (t *TopologyTest) SetUpSuite(c *gocheck.C) { 34 | t.numDCs = 3 35 | t.numNodes = 10 36 | t.replicationFactor = 3 37 | } 38 | 39 | func (t *TopologyTest) SetUpTest(c *gocheck.C) { 40 | t.localNID = node.NewNodeId() 41 | t.localDCID = DatacenterID("DC1") 42 | t.tp = NewTopology(t.localNID, t.localDCID, partitioner.NewMD5Partitioner(), 3) 43 | 44 | for i:=0; i (t.numNodes - t.replicationFactor) || i == 0 123 | c.Check(t.tp.TokenLocallyReplicated(tk), gocheck.Equals, shouldReplicate, gocheck.Commentf("%v: %v", i, tk)) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/types/b16.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // 16 byte value 8 | type B16 struct { 9 | b0 uint64 10 | b1 uint64 11 | } 12 | 13 | func (b *B16) MarshalBinary() ([]byte, error) { 14 | bs := make([]byte, 16) 15 | for i:=uint32(0);i<16;i++ { 16 | var src *uint64 17 | var offset uint32 18 | if i < 8 { 19 | src = &b.b0 20 | offset = i 21 | } else { 22 | src = &b.b1 23 | offset = i - 8 24 | } 25 | 26 | bs[i] = byte((*src >> ((7 - offset) * 8)) & 0xff) 27 | } 28 | return bs, nil 29 | } 30 | 31 | func (b *B16) UnmarshalBinary(bs []byte) error { 32 | if len(bs) != 16 { 33 | return fmt.Errorf("Exactly 16 bytes required for unmarshal, got %v", len(bs)) 34 | } 35 | for i:=uint32(0);i<16;i++ { 36 | var dst *uint64 37 | var offset uint32 38 | if i < 8 { 39 | dst = &b.b0 40 | offset = i 41 | } else { 42 | dst = &b.b1 43 | offset = i - 8 44 | } 45 | *dst |= uint64(bs[i]) << ((7 - offset) * 8) 46 | } 47 | return nil 48 | } 49 | 50 | func (b *B16) IsZero() bool { 51 | return b.b0 == 0 && b.b1 == 0 52 | } 53 | -------------------------------------------------------------------------------- /src/types/b16_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | 8 | import ( 9 | "launchpad.net/gocheck" 10 | ) 11 | 12 | type B16Test struct {} 13 | 14 | var _ = gocheck.Suite(&B16Test{}) 15 | 16 | func (t *B16Test) TestEncoding(c *gocheck.C) { 17 | uuidBytes := []byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} 18 | 19 | b := B16{} 20 | err := (&b).UnmarshalBinary(uuidBytes) 21 | c.Assert(err, gocheck.IsNil) 22 | 23 | marshalledBytes, err := (&b).MarshalBinary() 24 | c.Assert(err, gocheck.IsNil) 25 | 26 | c.Assert(bytes.Compare(uuidBytes, marshalledBytes), gocheck.Equals, 0) 27 | } 28 | 29 | func (t *B16Test) TestIsZero(c *gocheck.C) { 30 | c.Check((&B16{0, 0}).IsZero(), gocheck.Equals, true) 31 | c.Check((&B16{1, 0}).IsZero(), gocheck.Equals, false) 32 | c.Check((&B16{0, 1}).IsZero(), gocheck.Equals, false) 33 | c.Check((&B16{1, 1}).IsZero(), gocheck.Equals, false) 34 | } 35 | -------------------------------------------------------------------------------- /src/types/types_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | import ( 8 | "launchpad.net/gocheck" 9 | ) 10 | 11 | // Hook up gocheck into the "go test" runner. 12 | func Test(t *testing.T) { 13 | gocheck.TestingT(t) 14 | } 15 | -------------------------------------------------------------------------------- /src/types/uuid.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | ) 7 | 8 | import ( 9 | "code.google.com/p/go-uuid/uuid" 10 | ) 11 | 12 | import ( 13 | "serializer" 14 | ) 15 | 16 | const ( 17 | UUID_NUM_BYTES = 16 18 | ) 19 | 20 | // wraps up the google uuid library 21 | // and stores values as 2 uint64 values 22 | type UUID struct { 23 | B16 24 | } 25 | 26 | func NewUUID1() UUID { 27 | u := &UUID{} 28 | err := u.UnmarshalBinary([]byte(uuid.NewUUID())) 29 | if err != nil { 30 | panic(err) 31 | } 32 | return *u 33 | } 34 | 35 | func NewUUID4() UUID { 36 | u := &UUID{} 37 | err := u.UnmarshalBinary([]byte(uuid.NewRandom())) 38 | if err != nil { 39 | panic(err) 40 | } 41 | return *u 42 | } 43 | 44 | func (u UUID) Time() int64 { 45 | bs, err := (&u).MarshalBinary() 46 | if err != nil { 47 | panic(err) 48 | } 49 | uu := uuid.UUID(bs) 50 | t, _ := uu.Time() 51 | return int64(t) 52 | } 53 | 54 | func (u UUID) Bytes() []byte { 55 | bs, err := (&u).MarshalBinary() 56 | if err != nil { 57 | panic(err) 58 | } 59 | return bs 60 | } 61 | 62 | func (u UUID) String() string { 63 | bs, err := (&u).MarshalBinary() 64 | if err != nil { 65 | panic(err) 66 | } 67 | uu := uuid.UUID(bs) 68 | 69 | return uu.String() 70 | } 71 | 72 | func (u UUID) MarshalJSON() ([]byte, error) { 73 | return []byte("\"" + u.String() + "\""), nil 74 | } 75 | 76 | func (u *UUID) WriteBuffer(buf *bufio.Writer) error { 77 | if num, err := buf.Write(u.Bytes()); err != nil { 78 | return err 79 | } else if num != 16 { 80 | return fmt.Errorf("Expected 16 bytes written, got %v", num) 81 | } 82 | return nil 83 | } 84 | 85 | func (u *UUID) ReadBuffer(buf *bufio.Reader) error { 86 | bs, err := serializer.ReadBytes(buf, 16) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | if err = u.UnmarshalBinary(bs); err != nil { 92 | return err 93 | } 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /src/types/uuid_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | import ( 8 | "launchpad.net/gocheck" 9 | "code.google.com/p/go-uuid/uuid" 10 | ) 11 | 12 | type UUIDTest struct {} 13 | 14 | var _ = gocheck.Suite(&UUIDTest{}) 15 | 16 | func (s *UUIDTest) TestEncoding(c *gocheck.C) { 17 | uuidBytes := []byte(uuid.NewUUID()) 18 | c.Logf("%v", uuidBytes) 19 | 20 | u := UUID{} 21 | err := (&u).UnmarshalBinary(uuidBytes) 22 | c.Assert(err, gocheck.IsNil) 23 | 24 | marshalledBytes, err := (&u).MarshalBinary() 25 | c.Assert(err, gocheck.IsNil) 26 | c.Logf("%v", marshalledBytes) 27 | c.Logf("%v", u) 28 | 29 | c.Assert(bytes.Compare(uuidBytes, marshalledBytes), gocheck.Equals, 0) 30 | } 31 | 32 | func (s *UUIDTest) TestTime(c *gocheck.C) { 33 | extU := uuid.NewUUID() 34 | 35 | intU := UUID{} 36 | err := (&intU).UnmarshalBinary([]byte(extU)) 37 | c.Assert(err, gocheck.IsNil) 38 | 39 | extTime, _ := extU.Time() 40 | intTime := intU.Time() 41 | 42 | c.Logf("%v", intTime) 43 | c.Assert(int64(extTime), gocheck.Equals, intTime) 44 | c.Assert(intTime > 0, gocheck.Equals, true) 45 | } 46 | 47 | --------------------------------------------------------------------------------