3 changed files with 307 additions and 9 deletions
@ -0,0 +1,202 @@
@@ -0,0 +1,202 @@
|
||||
package simulation |
||||
|
||||
import "encoding/json" |
||||
|
||||
type Node struct { |
||||
ID string `json:"id"` |
||||
Type string `json:"type"` |
||||
Position Position `josn:"position"` |
||||
Props map[string]interface{} `json:"props"` |
||||
} |
||||
|
||||
type Position struct { |
||||
X int `json:"x"` |
||||
Y int `json:"y"` |
||||
} |
||||
|
||||
type Connection struct { |
||||
Source string `json:"source"` |
||||
Target string `json:"target"` |
||||
Label string `json:"label,omitempty"` |
||||
Direction string `json:"direction,omitempty"` |
||||
Protocol string `json:"protocol,omitempty"` |
||||
TLS bool `json:"tls,omitemity"` |
||||
Capacity int `json:"capacity,omitempty"` |
||||
} |
||||
|
||||
type Design struct { |
||||
Nodes []Node `json:"nodes"` |
||||
Connections []Connection |
||||
} |
||||
|
||||
type Cache struct { |
||||
Label string `json:"label"` |
||||
CacheTTL int `json:"cacheTTL"` |
||||
MaxEntries int `json:"maxEntries"` |
||||
EvictionPolicy string `json:"evictionPolicy"` |
||||
} |
||||
|
||||
type CDN struct { |
||||
Label string `json:"label"` |
||||
TTL int `json:"ttl"` |
||||
GeoReplication string `json:"geoReplication"` |
||||
CachingStrategy string `json:"cachingStrategy"` |
||||
Compression string `json:"compression"` |
||||
HTTP2 string `json:"http2"` |
||||
} |
||||
|
||||
type Database struct { |
||||
Label string `json:"label"` |
||||
Replication int `json:"replication"` |
||||
} |
||||
|
||||
type DataPipeline struct { |
||||
Label string `json:"label"` |
||||
BatchSize int `json:"batchSize"` |
||||
Transformation string `json:"transformation"` |
||||
} |
||||
|
||||
type LoadBalancer struct { |
||||
Label string `json:"label"` |
||||
Algorithm string `json:"algorithm"` |
||||
} |
||||
|
||||
type MessageQueue struct { |
||||
Label string `json:"label"` |
||||
QueueCapacity int `json:"queueCapacity"` |
||||
RetentionSeconds int `json:"retentionSeconds"` |
||||
} |
||||
|
||||
type Microservice struct { |
||||
Label string `json:"label"` |
||||
InstanceCount int `json:"instanceCount"` |
||||
CPU int `json:"cpu"` |
||||
RAMGb int `json:"ramGb"` |
||||
RPSCapacity int `json:"rpsCapacity"` |
||||
MonthlyUSD int `json:"monthlyUsd"` |
||||
ScalingStrategy string `json:"scalingStrategy"` |
||||
APIVersion string `json:"apiVersion"` |
||||
} |
||||
|
||||
type Monitoring struct { |
||||
Label string `json:"label"` |
||||
Tool string `json:"tool"` |
||||
AlertMetric string `json:"alertMetric"` // e.g., "cpu", "latency"
|
||||
ThresholdValue int `json:"thresholdValue"` // e.g., 80
|
||||
ThresholdUnit string `json:"thresholdUnit"` // e.g., "percent", "ms"
|
||||
} |
||||
|
||||
type ThirdPartyService struct { |
||||
Label string `json:"label"` |
||||
Provider string `json:"provider"` |
||||
Latency int `json:"latency"` |
||||
} |
||||
|
||||
type WebServer struct { |
||||
CPU int `json:"cpu"` |
||||
RamGb int `json:"ramGb"` |
||||
RPSCapacity int `json:"rpsCapacity"` |
||||
MonthlyCostUsd int `json:"monthlyCostUsd"` |
||||
} |
||||
|
||||
func (n *Node) UnmarshalJSON(data []byte) error { |
||||
type Alias Node // avoid infinite recursion
|
||||
aux := &struct { |
||||
Props json.RawMessage `json:"props"` |
||||
*Alias |
||||
}{ |
||||
Alias: (*Alias)(n), |
||||
} |
||||
|
||||
if err := json.Unmarshal(data, aux); err != nil { |
||||
return err |
||||
} |
||||
|
||||
switch n.Type { |
||||
case "cache": |
||||
var p Cache |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "cdn": |
||||
var p CDN |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "database": |
||||
var p Database |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "data pipeline": |
||||
var p DataPipeline |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "loadBalancer": |
||||
var p LoadBalancer |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "messageQueue": |
||||
var p MessageQueue |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "microservice": |
||||
var p Microservice |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "monitoring/alerting": |
||||
var p Monitoring |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "third party service": |
||||
var p ThirdPartyService |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
case "webserver": |
||||
var p WebServer |
||||
if err := json.Unmarshal(aux.Props, &p); err != nil { |
||||
return err |
||||
} |
||||
n.Props = structToMap(p) |
||||
|
||||
default: |
||||
var generic map[string]interface{} |
||||
if err := json.Unmarshal(aux.Props, &generic); err != nil { |
||||
return err |
||||
} |
||||
n.Props = generic |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func structToMap(v interface{}) map[string]interface{} { |
||||
data, _ := json.Marshal(v) |
||||
var result map[string]interface{} |
||||
json.Unmarshal(data, &result) |
||||
return result |
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
package level |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
type Level struct { |
||||
ID string `json:"id"` |
||||
Name string `json:"name"` |
||||
Description string `json:"description"` |
||||
Difficulty Difficulty `json:"difficulty"` |
||||
|
||||
TargetRPS int `json:"targetRps"` |
||||
DurationSec int `json:"durationSec"` |
||||
MaxMonthlyUSD int `json:"maxMonthlyUsd"` |
||||
MaxP95LatencyMs int `json:"maxP95LatencyMs"` |
||||
RequiredAvailabilityPct float64 `json:"requiredAvailabilityPct"` |
||||
|
||||
MustInclude []string `json:"mustInclude,omitempty"` |
||||
MustNotInclude []string `json:"mustNotInclude,omitempty"` |
||||
EncouragedComponents []string `json:"encouragedComponents,omitempty"` |
||||
DiscouragedComponents []string `json:"discouragedComponents,omitempty"` |
||||
MinReplicas map[string]int `json:"minReplicas,omitempty"` |
||||
MaxLatencyPerNodeType map[string]int `json:"maxLatencyPerNodeType,omitempty"` |
||||
CustomValidators []string `json:"customValidators,omitempty"` |
||||
|
||||
FailureEvents []FailureEvent `json:"failureEvents,omitempty"` |
||||
ScoringWeights map[string]float64 `json:"scoringWeights,omitempty"` |
||||
|
||||
Hints []string `json:"hints,omitempty"` |
||||
} |
||||
|
||||
type Difficulty string |
||||
|
||||
const ( |
||||
DifficultyEasy Difficulty = "easy" |
||||
DifficultyMedium Difficulty = "medium" |
||||
DifficultyHard Difficulty = "hard" |
||||
) |
||||
|
||||
var Registry map[string]map[string]Level |
||||
|
||||
type FailureEvent struct { |
||||
Type string `json:"type"` |
||||
TimeSec int `json:"timeSec"` |
||||
TargetID string `json:"targetId,omitempty"` |
||||
} |
||||
|
||||
func LoadLevels(path string) ([]Level, error) { |
||||
file, err := os.Open(path) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("Error opening levels.json: %w", err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
var levels []Level |
||||
err = json.NewDecoder(file).Decode(&levels) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("Error decoding levels.json: %w", err) |
||||
} |
||||
|
||||
return levels, nil |
||||
} |
||||
|
||||
func InitRegistry(levels []Level) { |
||||
Registry = make(map[string]map[string]Level) |
||||
for _, lvl := range levels { |
||||
// check if level already exists here
|
||||
if _, ok := Registry[lvl.Name]; !ok { |
||||
Registry[lvl.Name] = make(map[string]Level) |
||||
} |
||||
// populate it
|
||||
Registry[lvl.Name][string(lvl.Difficulty)] = lvl |
||||
} |
||||
} |
||||
|
||||
func GetLevel(name string, difficulty Difficulty) (*Level, error) { |
||||
diffMap, ok := Registry[name] |
||||
if !ok { |
||||
return nil, fmt.Errorf("level name %s not found", name) |
||||
} |
||||
|
||||
lvl, ok := diffMap[string(difficulty)] |
||||
if !ok { |
||||
return nil, fmt.Errorf("difficulty %s not available for level '%s'", difficulty, name) |
||||
} |
||||
return &lvl, nil |
||||
} |
||||
|
||||
func (d *Difficulty) UnmarshalJSON(b []byte) error { |
||||
var s string |
||||
if err := json.Unmarshal(b, &s); err != nil { |
||||
return err |
||||
} |
||||
|
||||
switch s { |
||||
case string(DifficultyEasy), string(DifficultyMedium), string(DifficultyHard): |
||||
*d = Difficulty(s) |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("invalid difficulty: %q", s) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue