Skip to content

Commit

Permalink
Merge pull request #2 from Yule-Labs/issue/1
Browse files Browse the repository at this point in the history
#1 Add base app
  • Loading branch information
Artemy Beliankin authored May 24, 2021
2 parents 1105cde + c7c2beb commit f72a209
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# common files
*.log
*.md

# git
**/.git/
**/.gitignore
**/.gitmodules

# configuration files
.gitlab-ci.yml
.dockerignore
.idea/

# artifacts
bin/

# misc
docs/
/coverage.out
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/
!bin/.gitkeep

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
vendor/

# Jetbrains directories
.idea/
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM golang:1.14.3-alpine3.11 as builder

ENV GIT_TERMINAL_PROMPT=1

RUN apk --update add git openssh && \
rm -rf /var/lib/apt/lists/* && \
rm /var/cache/apk/*

WORKDIR /ivy
COPY go.mod go.sum ./

RUN go mod download

COPY . ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin/ivy cmd/ivy/main.go

FROM alpine:3.11

ENV TZ=Europe/Moscow
RUN apk --update add bash tzdata && \
cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo ${TZ} > /etc/timezone

WORKDIR /usr/bin
COPY --from=builder /ivy/bin/ivy ./
COPY --from=builder /ivy/configs/ivy.conf.yml ./ivy.conf.yml
RUN chmod +x ./ivy

ENTRYPOINT ["/usr/bin/ivy"]
CMD ["-config", "./ivy-default.conf.yml"]
49 changes: 49 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ROOT_APP_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))

.PHONY: all
all: run

.PHONY: build
build: ## Build app
CGO_ENABLED=0 go build -o bin/ivy cmd/ivy/main.go

.PHONY: build-race
build-race: ## Build app with race flag
CGO_ENABLED=1 go build -race -o bin/ivyr cmd/ivy/main.go

.PHONY: run
run: build ## Build and run with default config
bin/ivy --config=configs/ivy-default.conf.yml

.PHONY: clean
clean: ## Clean bin dir
rm -rf bin/

.PHONY: run-race
run-race: build-race ## Build with race flag and run with default config
bin/ivyr --config=configs/ivy-default.conf.yml

.PHONY: fmt
fmt: ## Run go fmt and goimports
go fmt ./...
goimports -w ./

.PHONY: lint
lint: ## Run golangci-lint
golangci-lint run -v ./...

.PHONY: test
test: ## Run tests
@go test ./internal...

.PHONY: cover
cover: ## Run tests with cover
@go test -coverprofile=coverage.out ./internal...

.PHONY: coverr
coverr: cover ## Run tests with cover and open report
@go tool cover -html=coverage.out

.PHONY: help
help: ## List of commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
22 changes: 22 additions & 0 deletions cmd/ivy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"github.com/yule-labs/ivy/internal/server"
"github.com/yule-labs/ivy/internal/storage"
"log"
"os"
"os/signal"
"syscall"
)

func main() {
storage.InitStorage()

server.InitServer()

sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
interrupt := <-sig

log.Printf("Received system signal: %s. Shutting down redis-puf\n", interrupt)
}
Empty file added configs/ivy-default.conf.yml
Empty file.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/yule-labs/ivy

go 1.14
71 changes: 71 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package server

import (
"bufio"
"log"
"net"
)

type Server interface {
ListenAndServe() error
}

type defaultServer struct {
port string
network string
}

func (s *defaultServer) ListenAndServe() error {
listener, err := net.Listen(s.network, s.port)
if err != nil {
return err
}

for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}

go func() {
err := s.handleConn(conn)
if err != nil {
log.Println(err)
}
}()
}
}

func (s *defaultServer) handleConn(conn net.Conn) (err error) {
defer func() {
err := conn.Close()
if err != nil {
log.Println(err)
}
}()

message, _ := bufio.NewReader(conn).ReadString('\n')
response := s.handleMessage(message)

_, err = conn.Write([]byte(response))
return
}

func (s *defaultServer) handleMessage(msg string) string {
return "+OK\r\n"
}

func newServer() Server {
return &defaultServer{
port: ":9000",
network: "tcp",
}
}

func InitServer() {
err := newServer().ListenAndServe()
if err != nil {
panic(err)
}
}
47 changes: 47 additions & 0 deletions internal/storage/items.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package storage

import (
"time"
)

type DataType byte

const (
StringType DataType = 's'
IntType = 'i'
)

type Item struct {
key string
value interface{}
vtype DataType
ttl time.Time
}

func (i *Item) GetKey() string {
return i.key
}

func (i *Item) GetValue() interface{} {
return i.value
}

func NewItem(
key string,
value interface{},
vtype DataType,
ttl time.Time,
) *Item {
return &Item{
key: key,
value: value,
vtype: vtype,
ttl: ttl,
}
}

type Items map[string]*Item

func newItems() Items {
return make(Items)
}
69 changes: 69 additions & 0 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package storage

import (
"errors"
"sync"
"time"
)

var ErrorItemNotFound = errors.New("item not found")

var once sync.Once
var storage Storage

func GetStorage() Storage {
return storage
}

type Storage interface {
Set(key string, value *Item)
Get(key string) (item *Item, err error)
}

type defaultStorage struct {
// Sync locker
mu sync.RWMutex

// All items
items Items

// Expiration keys
tempItems *TempItems
}

func (s *defaultStorage) Set(key string, value *Item) {
now := time.Now().UnixNano()
s.mu.Lock()
defer s.mu.Unlock()
ttl := value.ttl.UnixNano()
if ttl > 0 {
if ttl < now {
return
}
go s.tempItems.Append(value)
}
s.items[key] = value
}

func (s *defaultStorage) Get(key string) (*Item, error) {
s.mu.RLock()
item, ok := s.items[key]
s.mu.RUnlock()
if ok {
return item, nil
}
return item, ErrorItemNotFound
}

func newDefaultStorage() Storage {
return &defaultStorage{
items: newItems(),
tempItems: newTempItems(),
}
}

func InitStorage() {
once.Do(func() {
storage = newDefaultStorage()
})
}
31 changes: 31 additions & 0 deletions internal/storage/temp_items.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package storage

import "sync"

type TempItems struct {
// Sync locker
mu sync.RWMutex

// Expiration queue
keys map[int][]string
}

func (t *TempItems) Append(value *Item) {
t.mu.Lock()
defer t.mu.Unlock()

ttl := value.ttl.Nanosecond()
if v, ok := t.keys[ttl]; ok {
t.keys[ttl] = append(v, value.key)
} else {
t.keys[ttl] = []string{
value.key,
}
}
}

func newTempItems() *TempItems {
return &TempItems{
keys: make(map[int][]string),
}
}

0 comments on commit f72a209

Please sign in to comment.