-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackend.go
More file actions
379 lines (308 loc) · 10.5 KB
/
backend.go
File metadata and controls
379 lines (308 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/*
Parallel and Distributed Systems
Project Part 4 Recipe Application
backend.go
MADE WITH LOVE BY MODOU NAING
*/
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)
// Recipe Data Type
type Recipe struct {
RecipeID uint64
Name string
Ingredients string
Directions string
Rating int
CreatedAt time.Time
}
// Message queue data structure
type Queue struct {
data []string
}
func (queue *Queue) isEmpty() bool {
return len(queue.data) == 0
}
func (queue *Queue) enqueue(element string) {
queue.data = append(queue.data, element)
}
func (queue *Queue) size() int {
return len(queue.data)
}
func (queue *Queue) dequeue() string {
element := queue.data[0]
if queue.size() > 1 {
queue.data = queue.data[1:]
} else {
queue.data = []string{}
}
return element
}
// Database of all recipies
var RECIPE_DB []Recipe
// Log of Messages
var LOG []string = []string{}
// Storage of other backend addresses
var BACKENDS []string
// Queue of all the messages
var messageQueue Queue
// Function to populate database with dummy data
func populateRecipieDB() {
RECIPE_DB = append(RECIPE_DB, Recipe{RecipeID: 0,
Name: "Chocolate Chip Cookie",
Ingredients: `1 cup Butter, 1 cup Sugar, 3 cups Flour, 1 tsp Sea Salt, 2 cups Chocolate Chips, 1 tsp Salt, 2 large eggs, 1 tsp baking soda`,
Directions: `0. Preheat oven to 375 degrees F,
1. In a separate bowl mix flour,
2. baking soda, salt, baking powder,
3. Cream together butter and sugars until combined,
4. Beat in eggs and vanilla until fluffy,
5. Mix in the dry ingredients until combined,
6. Add 12 oz package of chocolate chips and mix well,
7. Roll 2-3 TBS of dough at a time into balls and place them on your prepared cookie sheets,
8. Bake in preheated oven for approximately 8-10 minutes, Let them sit on the baking pan for 2 minutes`,
Rating: 2,
CreatedAt: time.Now(),
})
RECIPE_DB = append(RECIPE_DB, Recipe{RecipeID: 1,
Name: "Spahgetti and Meatballs",
Ingredients: `1lb spagetti, 1lb ground beef, 1 can crushed tomatoes, 2tbsp olive oil, 1 egg, 1/2 onion, red pepper flakes, chopped parsley, garlic cloves`,
Directions: `1. In a pot of boiling salted water, cook spaghetti. Drain.
2. In a large bowl, combine beef with bread crumbs, parsley, Parmesan, egg, garlic, 1 teaspoon salt, and red pepper flakes. Mix until just combined then form into 16 balls.
3. In a large pot over medium heat, heat oil. Add meatballs and cook, turning occasionally, until browned on all sides, about 10 minutes. Transfer meatballs to a plate.
4. Add onion to pot and cook until soft, 5 minutes. Add crushed tomatoes and bay leaf. Season with salt and pepper and bring to a simmer. Return meatballs to pot and cover. Simmer until sauce has thickened, 8 to 10 minutes
5. Serve pasta with a healthy scoop of meatballs and sauce. Top with Parmesan before serving.`,
Rating: 3,
CreatedAt: time.Now(),
})
RECIPE_DB = append(RECIPE_DB, Recipe{RecipeID: 2,
Name: "Chicken Noodle Soup",
Ingredients: `2lb chicken breast, chicken stock, 2tbsp butter, 1 diced onion, 2 carrots, 2 celery stalks, 2 cups egg noodles, black pepper`,
Directions: `1. Melt butter in a large stockpot or Dutch oven over medium heat. Add onion, carrots and celery. Cook, stirring occasionally, until tender, about 3-4 minutes. Stir in garlic until fragrant, about 1 minute.
2. Whisk in chicken stock and bay leaves; season with salt and pepper, to taste. Add chicken and bring to boil; reduce heat and simmer, covered, until the chicken is cooked through, about 30-40 minutes. Remove chicken and let cool before dicing into bite-size pieces, discarding bones.
3. Stir in chicken and pasta and cook until tender, about 6-7 minutes.
4. Remove from heat; stir in parsley, dill and lemon juice; season with salt and pepper, to taste.
5. Serve immediately.`,
Rating: 2,
CreatedAt: time.Now(),
})
}
// // Function to retriev the index of a recipe given the ID
func findRecipeIdx(recipeID uint64) int {
for idx, recipe := range RECIPE_DB {
if recipe.RecipeID == recipeID {
return idx
}
}
return -1
}
// Function to retrieve the next higest max ID
func retrieveID() uint64 {
var maxID uint64 = 0
for _, recipe := range RECIPE_DB {
if recipe.RecipeID > maxID {
maxID = recipe.RecipeID
}
}
return maxID + 1
}
// Function to retrieve the port from the command line arguments
func retrievePort(arguments []string) string {
port := arguments[2]
return port
}
// Function to handle the home route by returning all recipes
func handleHome(conn net.Conn) {
recipeJson, _ := json.Marshal(RECIPE_DB)
payload := string(recipeJson) + string("\n")
conn.Write([]byte(payload))
}
// Function to retrieve a given recipe
func handleGet(conn net.Conn, recipeIDString string) {
recipeID, _ := strconv.Atoi(recipeIDString)
idx := findRecipeIdx(uint64(recipeID))
recipeArr := []Recipe{RECIPE_DB[idx]}
recipeJson, _ := json.Marshal(recipeArr)
payload := string(recipeJson) + string("\n")
conn.Write([]byte(payload))
}
// Function to create a new recipe
func handleCreate(jsonString string) {
var recipe Recipe
json.Unmarshal([]byte(jsonString), &recipe)
recipe.RecipeID = retrieveID()
RECIPE_DB = append(RECIPE_DB, recipe)
}
// Function to edit recipe from DB
func handleEdit(recipeIDString, jsonString string) {
recipeID, _ := strconv.Atoi(recipeIDString)
recipeIdx := findRecipeIdx(uint64(recipeID))
jsonString = jsonString[:len(jsonString)-1]
var editedRecipe Recipe
json.Unmarshal([]byte(jsonString), &editedRecipe)
RECIPE_DB[recipeIdx] = editedRecipe
}
// Function to delete recipe from DB
func handleDelete(recipeIDString string) {
recipeID, _ := strconv.Atoi(recipeIDString)
indexToRemove := findRecipeIdx(uint64(recipeID))
// Assigning last element to current element
N := len(RECIPE_DB)
RECIPE_DB[indexToRemove] = RECIPE_DB[N-1]
RECIPE_DB = RECIPE_DB[:N-1]
}
// Function to contact other backends and sync logs
func syncLogs() {
for idx := 0; idx < len(BACKENDS); idx++ {
currConnection, err := net.Dial("tcp", BACKENDS[idx])
// Checking whether the backend is running
if err != nil {
continue
}
// Marshalling our Log to send to other backends
message := "SYNC~" //+ string(marshaledLog)
fmt.Fprintf(currConnection, message+"\n")
// We get the current backends log
response, _ := bufio.NewReader(currConnection).ReadString('\n')
var currLog []string
json.Unmarshal([]byte(response), &currLog)
// We then compare that size with ours to see if we have to reassign
if len(currLog) > len(LOG) {
LOG = currLog
executeMessagesInLog()
}
currConnection.Close()
}
}
// Function to handle the server connection given a client connection
func handleServerConnection(conn net.Conn) {
remoteAddr := conn.RemoteAddr().String()
log.Println("Client connected from", remoteAddr)
// variables used in PAXOS
var maxId int = 0
var proposalAccepted bool
var acceptedID int
var acceptedValue []byte
for {
message, _ := bufio.NewReader(conn).ReadString('\n')
dataList := strings.Split(string(message), "~")
if dataList[0] == "ALL" {
handleHome(conn)
} else if dataList[0] == "READ" {
recipeIDString := dataList[1][:len(dataList[1])-1]
handleGet(conn, recipeIDString)
} else if dataList[0] == "PREPARE" {
// Phase 1b: Promise
proposalNumber, _ := strconv.Atoi(dataList[1])
if proposalNumber > maxId {
conn.Write([]byte("FAIL" + "\n"))
} else {
maxId = proposalNumber
// Check whether the proposal was already accepted
if proposalAccepted {
payload := "PROMISE~" + string(maxId) + "~" + string(acceptedID) + "~" + string(acceptedValue)
conn.Write([]byte(payload + "\n"))
} else {
conn.Write([]byte("PROMISE~" + string(maxId) + "\n"))
}
}
} else if dataList[0] == "PROPOSE" {
proposalNumber, _ := strconv.Atoi(dataList[1])
if proposalNumber == maxId {
proposalAccepted = true
acceptedID = proposalNumber
acceptedValue = []byte(dataList[2])
command := dataList[2]
// Adding the current command to the message queue
messageQueue.enqueue(command)
// Adding messages to the log
LOG = append(LOG, command)
payload := "ACCEPT~" + string(acceptedValue)
conn.Write([]byte(payload + "\n"))
// Spawning a thread that executes the messages in message queue
go executeMessages()
// Contacting other backends to sync logs
go syncLogs()
} else {
conn.Write([]byte("FAIL" + "\n"))
}
} else if dataList[0] == "SYNC" {
marshaledLog, err := json.Marshal(LOG)
if err != nil {
fmt.Println("Error marshaling")
}
conn.Write([]byte(string(marshaledLog) + "\n"))
}
}
}
// Function to execute the messages in our log incase of a discrepancy
func executeMessagesInLog() {
// Erasing our database and executing messages in correct order
RECIPE_DB = []Recipe{}
populateRecipieDB()
for idx := 0; idx < len(LOG); idx++ {
currCommand := LOG[idx]
executeCommand(currCommand)
}
}
// Function to retrieve the other backend addresses within the system
func retrieveAddreses(arguments []string) []string {
var backends []string
backends = strings.Split(arguments[4], ",")
return backends
}
// Function to execute a given command from the frontend
func executeCommand(command string) {
dataList := strings.Split(string(command), "+")
if dataList[0] == "CREATE" {
jsonString := dataList[1]
handleCreate(jsonString)
} else if dataList[0] == "UPDATE" {
recipeIDString := dataList[1]
handleEdit(recipeIDString, dataList[2])
} else if dataList[0] == "DELETE" {
recipeIDString := dataList[1][:len(dataList[1])-1]
handleDelete(recipeIDString)
}
}
// Function to execute the messages in the message queue
func executeMessages() {
// Execute the messages in the queue while the queue isn't empty
for !messageQueue.isEmpty() {
message := messageQueue.dequeue()
executeCommand(message)
}
}
func main() {
populateRecipieDB()
arguments := os.Args
fmt.Println(arguments)
port := retrievePort(arguments)
BACKENDS = retrieveAddreses(arguments)
// Sync with other ones in case we crashed
go syncLogs()
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatal(err)
return
}
defer listener.Close()
fmt.Println("Listening on port #", port)
for {
fmt.Println("Backend Started......")
conn, err := listener.Accept()
if err != nil {
log.Fatal("Listener Error:", err)
}
// Handling individual server connections
go handleServerConnection(conn)
}
}