From ca71fd306106541218b7d93407f26b09f4628b7f Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Sat, 8 Apr 2023 13:58:47 -0600 Subject: [PATCH] Initial commit of event manager datastructures with some fake vex data. --- go.mod | 8 ++ go.sum | 19 ++++ graph.go | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 50 +++++++++ vex.go | 56 ++++++++++ 5 files changed, 466 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 graph.go create mode 100644 main.go create mode 100644 vex.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..00808cc --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module event_manager + +go 1.20 + +require ( + github.com/google/uuid v1.3.0 // indirect + github.com/graph-gophers/graphql-go v1.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a03f06c --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/graph.go b/graph.go new file mode 100644 index 0000000..a66fef6 --- /dev/null +++ b/graph.go @@ -0,0 +1,333 @@ +package main + +import ( + "fmt" + "errors" + "sync" + graphql "github.com/graph-gophers/graphql-go" + "github.com/google/uuid" +) + +func gql_randid() graphql.ID{ + uuid_str := uuid.New().String() + return graphql.ID(uuid_str) +} + +type GraphNode interface { + Name() string + Description() string + ID() graphql.ID + UpdateListeners() error + UpdateChannel() chan error + Update() error +} + +type BaseNode struct { + name string + description string + id graphql.ID + listeners []chan error + listeners_lock sync.Mutex +} + +func (node * BaseNode) Name() string { + return node.name +} + +func (node * BaseNode) Description() string { + return node.description +} + +func (node * BaseNode) ID() graphql.ID { + return node.id +} + +// Create a new listener channel for the node, add it to the nodes listener list, and return the new channel +func (node * BaseNode) UpdateChannel() chan error { + new_listener := make(chan error, 1) + node.listeners_lock.Lock() + node.listeners = append(node.listeners, new_listener) + node.listeners_lock.Unlock() + return new_listener +} + +// Send the update to listener channels +func (node * BaseNode) UpdateListeners() error { + closed_listeners := []int{} + listeners_closed := false + + // Send each listener nil to signal it to check for new content + // if the first attempt to send it fails close the listener + node.listeners_lock.Lock() + for i, listener := range node.listeners { + select { + case listener <- nil: + default: + close(listener) + closed_listeners = append(closed_listeners, i) + listeners_closed = true + } + } + + // If any listeners have been closed, loop over the listeners + // Add listeners to the "remaining" list if i insn't in closed_listeners + if listeners_closed == true { + remaining_listeners := []chan error{} + for i, listener := range node.listeners { + listener_closed := false + for _, index := range closed_listeners { + if index == i { + listener_closed = true + break + } + } + if listener_closed == false { + remaining_listeners = append(remaining_listeners, listener) + } + } + + node.listeners = remaining_listeners + } + node.listeners_lock.Unlock() + + return nil +} + +func (node * BaseNode) Update() error { + return node.UpdateListeners() +} + +// Resources propagate update up to multiple parents, and not downwards +// (subscriber to team won't get update to alliance, but subscriber to alliance will get update to team) +func (resource * BaseResource) Update() error { + err := resource.UpdateListeners() + if err != nil { + return err + } + + for _, parent := range resource.Parents() { + err := parent.Update() + if err != nil { + return err + } + } + + return nil +} + +// Update the events listeners, and notify the parent to do the same +func (event * BaseEvent) Update() error { + err := event.UpdateListeners() + if err != nil { + return err + } + + if event.parent != nil{ + return event.parent.Update() + } + return nil +} + +type Resource interface { + GraphNode + AddParent(parent Resource) error + Children() []Resource + Parents() []Resource +} + +type BaseResource struct { + BaseNode + update_channel chan error + parents []Resource + children []Resource +} + +func (resource * BaseResource) Children() []Resource { + return resource.children +} + +func (resource * BaseResource) Parents() []Resource { + return resource.parents +} + +func (resource * BaseResource) AddParent(parent Resource) error { + // Don't add self as parent + if parent.ID() == resource.ID() { + error_str := fmt.Sprintf("Will not add %s as parent of itself", parent.ID) + return errors.New(error_str) + } + + // Don't add parent if it's already a parent + for _, p := range resource.parents { + if p.ID() == parent.ID() { + error_str := fmt.Sprintf("%s is already a parent of %s, will not double-bond", p.ID(), resource.ID()) + return errors.New(error_str) + } + } + + // Add the parent + resource.parents = append(resource.parents, parent) + return nil +} + +type Event interface { + GraphNode + Children() []Event + Parent() Event + RegisterParent(parent Event) error + RequiredResources() []Resource + CreatedResources() []Resource + AddChild(child Event) error + FindChild(id graphql.ID) Event +} + +type BaseEvent struct { + BaseNode + locked_resources []Resource + created_resources []Resource + required_resources []Resource + children []Event + parent Event +} + +func NewBaseEvent(name string, description string, required_resources []Resource) * BaseEvent { + event := &BaseEvent{ + BaseNode: BaseNode{ + name: name, + description: description, + id: gql_randid(), + listeners: []chan error{}, + }, + parent: nil, + locked_resources: []Resource{}, + created_resources: []Resource{}, + required_resources: required_resources, + } + + return event +} + +// Store the nodes parent for upwards propagation of changes +func (event * BaseEvent) RegisterParent(parent Event) error{ + if event.parent != nil { + return errors.New("Parent already registered") + } + + event.parent = parent + return nil +} + +func (event * BaseEvent) Parent() Event { + return event.parent +} + +func (event * BaseEvent) RequiredResources() []Resource { + return event.required_resources +} + +func (event * BaseEvent) CreatedResources() []Resource { + return event.created_resources +} + +func (event * BaseEvent) Children() []Event { + return event.children +} + +func (event * BaseEvent) FindChild(id graphql.ID) Event { + if id == event.ID() { + return event + } + + for _, child := range event.Children() { + result := child.FindChild(id) + if result != nil { + return result + } + } + + return nil +} + +func (event * BaseEvent) AddChild(child Event) error { + err := child.RegisterParent(event) + if err != nil { + error_str := fmt.Sprintf("Failed to register %s as a parent of %s, cancelling AddChild", event.ID(), child.ID()) + return errors.New(error_str) + } + + event.children = append(event.children, child) + return nil +} + +type EventManager struct { + dag_nodes map[graphql.ID]Resource + root_event Event +} + +func NewEventManager() * EventManager { + state := &EventManager{ + dag_nodes: map[graphql.ID]Resource{}, + root_event: nil, + } + return state; +} + +func (manager * EventManager) AddResource(resource Resource) error { + _, exists := manager.dag_nodes[resource.ID()] + if exists == true { + error_str := fmt.Sprintf("%s is already in the resource DAG, cannot add again") + return errors.New(error_str) + } + + for _, child := range resource.Children() { + _, exists := manager.dag_nodes[child.ID()] + if exists == false { + error_str := fmt.Sprintf("%s is not in the resource DAG, cannot add %s to DAG", child.ID(), resource.ID()) + return errors.New(error_str) + } + } + manager.dag_nodes[resource.ID()] = resource + for _, child := range resource.Children() { + child.AddParent(resource) + } + return nil +} + +// Check that the node doesn't already exist in the tree +// Check the the selected parent exists in the tree +// Check that required resources exist in the DAG +// Check that created resources don't exist in the DAG +// Add resources created by the event to the DAG +// Add child to parent +func (manager * EventManager) AddEvent(parent Event, child Event) error { + if manager.root_event.FindChild(child.ID()) != nil { + error_str := fmt.Sprintf("Event %s already exists in the event tree, can not add again", child.ID()) + return errors.New(error_str) + } + + if manager.root_event.FindChild(parent.ID()) == nil { + error_str := fmt.Sprintf("Event %s is not present in the event tree, cannot add %s as child", parent.ID(), child.ID()) + return errors.New(error_str) + } + + for _, resource := range child.RequiredResources() { + _, exists := manager.dag_nodes[resource.ID()] + if exists == false { + error_str := fmt.Sprintf("Required resource %s not in DAG, cannot add event %s", resource.ID(), child.ID()) + return errors.New(error_str) + } + } + + for _, resource := range child.CreatedResources() { + _, exists := manager.dag_nodes[resource.ID()] + if exists == true { + error_str := fmt.Sprintf("Created resource %s already exists in DAG, cannot add event %s", resource.ID(), child.ID()) + return errors.New(error_str) + } + } + + parent.AddChild(child) + return nil +} + + diff --git a/main.go b/main.go new file mode 100644 index 0000000..718ab75 --- /dev/null +++ b/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "log" +) + +func fake_data() * EventManager { + event_manager := NewEventManager() + + teams := []*Team{} + teams = append(teams, NewTeam("6659", "A", []string{"jimmy"})) + teams = append(teams, NewTeam("6659", "B", []string{"timmy"})) + teams = append(teams, NewTeam("6659", "C", []string{"grace"})) + teams = append(teams, NewTeam("6659", "D", []string{"jeremy"})) + teams = append(teams, NewTeam("210", "W", []string{"bobby"})) + teams = append(teams, NewTeam("210", "X", []string{"toby"})) + teams = append(teams, NewTeam("210", "Y", []string{"jennifer"})) + teams = append(teams, NewTeam("210", "Z", []string{"emily"})) + teams = append(teams, NewTeam("315", "W", []string{"bobby"})) + teams = append(teams, NewTeam("315", "X", []string{"toby"})) + teams = append(teams, NewTeam("315", "Y", []string{"jennifer"})) + teams = append(teams, NewTeam("315", "Z", []string{"emily"})) + + for _, team := range teams { + err := event_manager.AddResource(team) + if err != nil { + log.Print(err) + } + } + + + alliances := []Resource{} + for i, team := range teams[:len(teams)-1] { + for _, team2 := range teams[i+1:] { + alliance := NewAlliance(team, team2) + alliances = append(alliances, alliance) + err := event_manager.AddResource(alliance) + if err != nil { + log.Print(err) + } + } + } + + return event_manager +} + +func main() { + event_manager := fake_data() + log.Printf("Starting event_manager: %+v", event_manager) +} diff --git a/vex.go b/vex.go new file mode 100644 index 0000000..768466f --- /dev/null +++ b/vex.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" +) + +type Team struct { + BaseResource + Members []string + Org string + Team string +} + +func NewTeam(org string, team string, members []string) * Team { + name := fmt.Sprintf("%s%s", org, team) + description := fmt.Sprintf("Team %s", name) + resource := &Team{ + BaseResource: BaseResource{ + BaseNode: BaseNode{ + name: name, + description: description, + id: gql_randid(), + listeners: []chan error{}, + }, + parents: []Resource{}, + children: []Resource{}, + }, + Members: members, + Org: org, + Team: team, + } + return resource +} + +type Alliance struct { + BaseResource +} + +func NewAlliance(team0 * Team, team1 * Team) * Alliance { + name := fmt.Sprintf("Alliance %s/%s", team0.Name(), team1.Name()) + description := "" + + resource := &Alliance{ + BaseResource: BaseResource{ + BaseNode: BaseNode{ + name: name, + description: description, + id: gql_randid(), + listeners: []chan error{}, + }, + parents: []Resource{}, + children: []Resource{team0, team1}, + }, + } + return resource +}