Skip to content

Commit 1248fbe

Browse files
Moved project to required for submission repo
1 parent 4cec72c commit 1248fbe

File tree

14 files changed

+688
-0
lines changed

14 files changed

+688
-0
lines changed

Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# https://hub.docker.com/_/golang/
2+
FROM golang:1.11.3-alpine3.8
3+
4+
RUN mkdir -p /go/src/github.com/ITandElectronics/GoHomework
5+
WORKDIR /go/src/github.com/ITandElectronics/GoHomework
6+
COPY . .
7+
8+
RUN CGO_ENABLED=0 go test -v ./...
9+
10+
RUN CGO_ENABLED=0 go install -v ./...
11+
12+
FROM alpine:3.8
13+
14+
WORKDIR /root/
15+
COPY --from=0 /go/bin/client .
16+
COPY --from=0 /go/bin/server .
17+
18+
CMD ["./server"]

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
build:
2+
CGO_ENABLED=0 go install ./...
3+
docker build -t redislight .
4+
test:
5+
go test -coverprofile=./coverage.profile ./...
6+
go tool cover --func=./coverage.profile > coverage.out
7+
check:
8+
go get -u golang.org/x/lint/golint
9+
go get -u golang.org/x/tools/cmd/goimports
10+
11+
${GOPATH}/bin/goimports -w .
12+
go vet ./...
13+
${GOPATH}/bin/golint ./...
14+
run:
15+
CGO_ENABLED=0 go run ./cmd/server

Project.pdf

140 KB
Binary file not shown.

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
## Redislight
2+
### Simplified version of a redis. Supports only GET, SET and DEL commands
3+
4+
## Usage
5+
### Pre Requisites
6+
- Docker
7+
8+
Redis light consists of two application: client and server. They both bundled into a single docker image which can be built by running following command:
9+
```bash
10+
$ docker build -t redislight .
11+
```
12+
### Server
13+
In order to run server:
14+
```bash
15+
$ docker run -p 9090:9090 redislight
16+
```
17+
Server supports following options:
18+
```bash
19+
./server --help
20+
Usage of server:
21+
--mode, -m string
22+
Storage options. One of [disk] (default "disk")
23+
--port, -p int
24+
Port to listen on (default 9090)
25+
```
26+
27+
### Client
28+
In order to run client:
29+
```bash
30+
$ docker run redislight ./client
31+
```
32+
33+
Client supports following options:
34+
```bash
35+
./client --help
36+
Usage of client:
37+
--host, -h string
38+
Remote server address (default "127.0.0.1")
39+
--port, -p int
40+
Remote server port (default 9090)
41+
```
42+
43+
### Supported commands
44+
**GET** *key* - return value associated with proveded *key* or 'key is not exists' error
45+
**SET** *key* *value* - create or update *value* associated with the *key*
46+
**DEL** *key* - remove value associated with the *key*. If *key* is not exists, it will return 'not exists error'
47+
48+
49+
## Development
50+
### Pre Requisites
51+
- Go >= 1.11
52+
- Docker
53+
- Make
54+
55+
Clone this repository into GOPATH/src/{repository}/redislight:
56+
```bash
57+
$ git clone .../redislight.git
58+
```
59+
Run linters:
60+
```bash
61+
$ make check
62+
```
63+
Run tests(also will produce code coverage in coverage.out file):
64+
```bash
65+
$ make tests
66+
```
67+
Build:
68+
```bash
69+
$ make build
70+
```
71+
72+
Run server:
73+
```bash
74+
$ make run
75+
```

cmd/client/main.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"log"
9+
"net"
10+
"os"
11+
12+
"github.com/ITandElectronics/GoHomework/protocol"
13+
)
14+
15+
func main() {
16+
var (
17+
host string
18+
port int
19+
)
20+
21+
flag.IntVar(&port, "port", 9090, "Remote server port")
22+
flag.IntVar(&port, "p", 9090, "Remote server port")
23+
flag.StringVar(&host, "host", "127.0.0.1", "Remote server address")
24+
flag.StringVar(&host, "h", "127.0.0.1", "Remote server address")
25+
flag.Usage = func() {
26+
usage := `Usage of %s:
27+
--host, -h string
28+
Remote server address (default "127.0.0.1")
29+
--port, -p int
30+
Remote server port (default 9090)
31+
`
32+
fmt.Fprintf(os.Stderr, usage, os.Args[0])
33+
}
34+
flag.Parse()
35+
36+
conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", host, port))
37+
if err != nil {
38+
log.Fatalf("couldn't connect to server: %v\n", err)
39+
}
40+
41+
input := bufio.NewReader(os.Stdin)
42+
response := bufio.NewReader(conn)
43+
for {
44+
fmt.Print("Enter command: ")
45+
line, _, err := input.ReadLine()
46+
if err != nil {
47+
fmt.Printf("[error] couldn't read line from stdin: %v\n", err)
48+
continue
49+
}
50+
msg, err := protocol.DecodeMessage(line)
51+
if err != nil {
52+
fmt.Printf("[error] invalid message format: %v\n", err)
53+
continue
54+
}
55+
if err := protocol.ValidateMessage(msg); err != nil {
56+
fmt.Printf("validation failed: %v\n", err)
57+
continue
58+
}
59+
if _, err = conn.Write(append(line, '\n')); err != nil {
60+
fmt.Printf("[error] coudn't write to connection: %v\n", err)
61+
continue
62+
}
63+
64+
resp, _, err := response.ReadLine()
65+
if err != nil {
66+
if err == io.EOF {
67+
fmt.Println("connection is closed")
68+
return
69+
}
70+
fmt.Printf("[error] couldn't read response: %v\n", err)
71+
continue
72+
}
73+
fmt.Printf("[server] %s\n", string(resp))
74+
}
75+
76+
}

cmd/server/main.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"os"
8+
9+
"github.com/ITandElectronics/GoHomework/disk"
10+
"github.com/ITandElectronics/GoHomework/server"
11+
)
12+
13+
func main() {
14+
const storagePath = "./db.json"
15+
16+
var (
17+
port int
18+
mode string
19+
)
20+
21+
flag.IntVar(&port, "port", 9090, "Port to listen on")
22+
flag.IntVar(&port, "p", 9090, "Port to listen on")
23+
flag.StringVar(&mode, "mode", "disk", "Storage options. One of [disk]")
24+
flag.StringVar(&mode, "m", "disk", "Storage options. One of [disk]")
25+
flag.Usage = func() {
26+
usage := `Usage of %s:
27+
--mode, -m string
28+
Storage options. One of [disk] (default "disk")
29+
--port, -p int
30+
Port to listen on (default 9090)
31+
`
32+
fmt.Fprintf(os.Stderr, usage, os.Args[0])
33+
34+
}
35+
flag.Parse()
36+
37+
fmt.Printf("server is going to start on '%d' and work in '%s' mode\n", port, mode)
38+
storage, err := disk.New(storagePath)
39+
if err != nil {
40+
log.Fatalf("coudn't create stroage: %v\n", err)
41+
}
42+
s, err := server.New(port, storage)
43+
if err != nil {
44+
log.Fatalf("coudln't create server: %v\n", err)
45+
}
46+
log.Fatalf("unable to start server: %v\n", s.Run())
47+
}

cmd/server/server

3.24 MB
Binary file not shown.

disk/disk.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package disk
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"os"
8+
"sync"
9+
10+
redislight "github.com/ITandElectronics/GoHomework"
11+
)
12+
13+
// New construct new storage
14+
func New(path string) (*Disk, error) {
15+
fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(0640))
16+
if err != nil {
17+
return nil, err
18+
}
19+
data := make(map[string]string)
20+
if err := json.NewDecoder(fd).Decode(&data); err != nil && err != io.EOF {
21+
return nil, err
22+
}
23+
return &Disk{
24+
m: &sync.RWMutex{},
25+
fd: fd,
26+
data: data,
27+
}, nil
28+
}
29+
30+
// Disk represent disk storage
31+
type Disk struct {
32+
m *sync.RWMutex
33+
fd *os.File
34+
data map[string]string
35+
}
36+
37+
// Get checks whether key exists and either return appropriate value or KeyIsNotExists error
38+
func (d *Disk) Get(key string) (string, error) {
39+
d.m.RLock()
40+
defer d.m.RUnlock()
41+
val, ok := d.data[key]
42+
if !ok {
43+
return "", redislight.ErrKeyIsNotExists
44+
}
45+
return val, nil
46+
}
47+
48+
// Set create new or update existing key value
49+
func (d *Disk) Set(key, value string) error {
50+
d.m.Lock()
51+
defer d.m.Unlock()
52+
d.data[key] = value
53+
return d.sync()
54+
}
55+
56+
// Del remove entry related to provided key from the storage
57+
func (d *Disk) Del(key string) error {
58+
d.m.Lock()
59+
defer d.m.Unlock()
60+
if _, ok := d.data[key]; !ok {
61+
return redislight.ErrKeyIsNotExists
62+
}
63+
delete(d.data, key)
64+
return d.sync()
65+
}
66+
67+
// all sync calls should be protected by mutex
68+
func (d *Disk) sync() error {
69+
_, err := d.fd.Seek(0, 0)
70+
if err != nil {
71+
return fmt.Errorf("coudn't seek to the start of the file: %v", err)
72+
}
73+
return json.NewEncoder(d.fd).Encode(d.data)
74+
}

disk/disk_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package disk
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"testing"
8+
9+
redislight "github.com/ITandElectronics/GoHomework"
10+
)
11+
12+
func createTempDir(t *testing.T) string {
13+
dir, err := ioutil.TempDir("", "redis-light-tests")
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
return dir
18+
}
19+
20+
func TestNew(t *testing.T) {
21+
dir := createTempDir(t)
22+
defer os.RemoveAll(dir)
23+
24+
_, err := New(fmt.Sprintf("%s/test_db.json", dir))
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
}
29+
30+
func TestDiskGet(t *testing.T) {
31+
disk, err := New("./fixtures/db.json")
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
_, err = disk.Get("not_existing_key")
36+
if err == nil {
37+
t.Fatal("should return error")
38+
}
39+
if err != redislight.ErrKeyIsNotExists {
40+
t.Fatalf("%v - is not expected", err)
41+
}
42+
val, err := disk.Get("exists")
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
if val == "" {
47+
t.Fatal("should return some value")
48+
}
49+
50+
}
51+
52+
func TestDiskSet(t *testing.T) {
53+
disk, err := New("./fixtures/db.json")
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
err = disk.Set("new", "world")
58+
if err != nil {
59+
t.Fatal(err)
60+
}
61+
val, err := disk.Get("new")
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if val != "world" {
66+
t.Fatal("Set is not working")
67+
}
68+
69+
}
70+
71+
func TestDiskDel(t *testing.T) {
72+
73+
}
74+
75+
func TestDiskSync(t *testing.T) {
76+
77+
}

0 commit comments

Comments
 (0)