New class link to handle links between components and possible errors

This commit is contained in:
pb 2024-04-16 16:12:54 +02:00
parent 99278ff7ef
commit d91afc2acd
5 changed files with 240 additions and 100 deletions

View File

@ -2,11 +2,12 @@
- [ ] In most of the components from 'models/' we have a method to add input and output to the model, however this linking of components is already done in oc-schedule when parsing the MxGraph. We need to determine if adding relations between components inside the objects themself is necessary. - [ ] In most of the components from 'models/' we have a method to add input and output to the model, however this linking of components is already done in oc-schedule when parsing the MxGraph. We need to determine if adding relations between components inside the objects themself is necessary.
- When running in debug mode with a breakpoint inside the first line of computing.addLink it is only called once - When running in debug mode with a breakpoint inside the first line of computing.addLink it is only called once
- [ ]
## MxGraph ## MxGraph
- [ ] The ConsumeMxGraphModel is way too long, it should refactored and broken down in different sub methods - [ ] The ConsumeMxGraphModel is way too long, it should refactored and broken down in different sub methods
- mxcell are put inside an <object> tag when the settings have been opened, wether values have been set or not. Maybe we could find a way to make mxgraph add these whenever we add a component to the graph. - mxcell are put inside an <object> tag when the settings have been opened, wether values have been set or not. Maybe we could find a way to make mxgraph add these whenever we add a component to the graph.
- then identify the links only - then identify the links only
- [ ] It is unclear what are the inputs and the ouputs. It seems like they were implemented to link two components, but it seems redundant with the identification of links - [ ] It is unclear what are the inputs and the ouputs. It seems like they were implemented to link two components, but it seems redundant with the identification of links
- This has been potentially tackled with the creation of a class to handle links between components. The components do no handle their own connections with other components, this task is delegated to the Link and Worlflow classes.

25
docs/linking_errors.md Normal file
View File

@ -0,0 +1,25 @@
# Handling errors during workflows' post
Every time an user modify a worflow through oc-search's GUI its representation is sent through an XML to oc-catalog API.
To ensure a correct execution of the workflow we must look for irregularities in the workflow's design. Of course an error can be the result of a workflow being currently built by the user, with the corrective action coming.
This document aims at laying down all the requirements that each component must respect and show wether they have been implemented in the code or not.
## Computing
- [x] A computing component must be paired with a datacenter component
## Data
- [x] A data component must be linked to at least one computing component
## Datacenter
- [x] A datacenter component must be linked to at least one computing component
## Storage
- [x] A storage component must have at least one target or be the source of another component

View File

@ -14,17 +14,38 @@ type Link struct {
// Use ResourceObject parameter to process certain components type differently // Use ResourceObject parameter to process certain components type differently
// and Id's to identify each component as a node in an oriented graph // and Id's to identify each component as a node in an oriented graph
func NewLink(src ResourceObject, srcId string, dst ResourceObject, dstId string) (link Link) { // In the case of DCLink we choose to always consider the DC as the destination
// in order to facilitate some logic
func NewLink(src ResourceObject, srcId string, dst ResourceObject, dstId string) (link Link) {
link.Source = srcId link.Source = srcId
link.Destination = dstId link.Destination = dstId
// If the link is between a DC and a component make sure that the DC is destination
// and if the component is computing, update the DataCenterID
if (src.getRtype() == rtype.DATACENTER || dst.getRtype() == rtype.DATACENTER){ if (src.getRtype() == rtype.DATACENTER || dst.getRtype() == rtype.DATACENTER){
var linked ResourceObject
link.DCLink = true link.DCLink = true
if src.getRtype() == rtype.DATACENTER {
linked = dst
} else {
linked = src
}
if( link.DCLink && src.getRtype() == rtype.DATACENTER){
link.Destination = srcId
link.Source = dstId
}
if (linked.getRtype() == rtype.COMPUTING){
linked.(*ComputingObject).DataCenterID = link.Destination
}
} }
return return
} }

View File

@ -41,15 +41,35 @@ type MxObject struct {
// Didn't manage to differentiate Links and cells containing components using // Didn't manage to differentiate Links and cells containing components using
// only structures and unmarshal, so we use this method post-umarshalling // only structures and unmarshal, so we use this method post-umarshalling
func (g *MxGraphModel) createLinks() error { func (g *MxGraphModel) createLinks() {
var cells_without_links []MxCell
for i, mxcell := range g.Root.MxCell { for i, mxcell := range g.Root.MxCell {
if mxcell.Edge != nil {
if mxcell.Edge != nil {
mxcell.processLinks()
newLink := MxLink{mxcell.ID,*mxcell.Source,*mxcell.Target} newLink := MxLink{mxcell.ID,*mxcell.Source,*mxcell.Target}
g.Root.MxLink = append(g.Root.MxLink,newLink) g.Root.MxLink = append(g.Root.MxLink,newLink)
g.Root.MxCell = append(g.Root.MxCell[:i],g.Root.MxCell[i+1:]...) } else {
cells_without_links = append(cells_without_links,g.Root.MxCell[i])
} }
} }
return nil
g.Root.MxCell = nil
g.Root.MxCell = cells_without_links
}
func (cell *MxCell) processLinks() {
v := ""
if cell.Source == nil {
cell.Source = &v
}
if cell.Target == nil {
cell.Target = &v
}
} }
type mxissue struct { type mxissue struct {

View File

@ -546,12 +546,8 @@ func ParseMxGraph(username, workflowName, xmlData string) (err error, mxissues [
return err, nil return err, nil
} }
err = xmlModel.createLinks() xmlModel.createLinks()
if err != nil {
logs.Alert("Error creating links")
return err, nil
}
targetWorkspaceWorkflow, err, mxissues := userWorkspace.ConsumeMxGraphModel(xmlModel) targetWorkspaceWorkflow, err, mxissues := userWorkspace.ConsumeMxGraphModel(xmlModel)
if err != nil { if err != nil {
@ -652,6 +648,23 @@ func (ws Workspace) ConsumeMxGraphModel(xmlmodel MxGraphModel) (returned_wf *Wor
// issues = append(issues, errors.New("MxCell with ID "+cell.ID+" doesn't have a valid link")) // issues = append(issues, errors.New("MxCell with ID "+cell.ID+" doesn't have a valid link"))
default: default:
// Not root nor resource. Should be only links
sourceObj := returned_wf.GetResource(cell.Source)
targetObj := returned_wf.GetResource(cell.Target)
if sourceObj == nil || targetObj == nil {
if sourceObj == nil && targetObj == nil {
issues = append(issues, errors.New("Arrow "+cell.ID+" is alone"))
} else if sourceObj == nil {
issues = append(issues, errors.New("Arrow ("+cell.ID+") to "+*targetObj.getName()+" without parent"))
} else {
issues = append(issues, errors.New("Arrow "+cell.ID+" from "+*sourceObj.getName()+" without target"))
}
// If is a invalid link, we can't save it in the DB
continue
}
// Not root nor resource. Should be only links // Not root nor resource. Should be only links
// If is a invalid link, we can't save it in the DB // If is a invalid link, we can't save it in the DB
// We should always get a ID because we already registered resources and discarded which doesn't correspond to existent models // We should always get a ID because we already registered resources and discarded which doesn't correspond to existent models
@ -665,58 +678,59 @@ func (ws Workspace) ConsumeMxGraphModel(xmlmodel MxGraphModel) (returned_wf *Wor
} }
} }
issues = ws.CreateLinks(returned_wf,xmlmodel.Root.MxLink, issues) issues = returned_wf.CreateLinks(xmlmodel.Root.MxLink, issues)
issues = returned_wf.CheckLinks(issues)
dcslist := make(map[string]bool) // dcslist := make(map[string]bool)
dataslist := make(map[string]bool) // dataslist := make(map[string]bool)
// datalist := make(map[string]bool) // // datalist := make(map[string]bool)
// Test wether the computing components are linked with a DC // // Test wether the computing components are linked with a DC
for _, comp := range returned_wf.Computing { // for _, comp := range returned_wf.Computing {
if comp.DataCenterID == "" { // if comp.DataCenterID == "" {
issues = append(issues, errors.New("Computing "+*comp.getName()+" without a Datacenter")) // issues = append(issues, errors.New("Computing "+*comp.getName()+" without a Datacenter"))
} else { // } else {
// If doesn't exist in the list, means is new element to register as used // // If doesn't exist in the list, means is new element to register as used
dcslist[comp.DataCenterID] = true // dcslist[comp.DataCenterID] = true
} // }
for _, dcin := range comp.Inputs { // for _, dcin := range comp.Inputs {
switch returned_wf.GetResource(&dcin).getRtype() { // switch returned_wf.GetResource(&dcin).getRtype() {
case rtype.DATA: // case rtype.DATA:
dataslist[dcin] = true // dataslist[dcin] = true
} // }
} // }
for _, dcout := range comp.Outputs { // for _, dcout := range comp.Outputs {
switch returned_wf.GetResource(&dcout).getRtype() { // switch returned_wf.GetResource(&dcout).getRtype() {
case rtype.DATA: // case rtype.DATA:
dataslist[dcout] = true // dataslist[dcout] = true
} // }
} // }
} // }
for _, storage_component := range returned_wf.Storage { // for _, storage_component := range returned_wf.Storage {
if storage_component.Inputs == nil && storage_component.Outputs == nil { // if storage_component.Inputs == nil && storage_component.Outputs == nil {
issues = append(issues, errors.New("Storage "+*storage_component.getName()+" without compatible inputs and outputs")) // issues = append(issues, errors.New("Storage "+*storage_component.getName()+" without compatible inputs and outputs"))
} // }
} // }
for dcID, dc_component := range returned_wf.Datacenter { // for dcID, dc_component := range returned_wf.Datacenter {
// if rID doesn't exist in the list, it means that it's not used // // if rID doesn't exist in the list, it means that it's not used
if _, ok := dcslist[dcID]; !ok { // if _, ok := dcslist[dcID]; !ok {
issues = append(issues, errors.New("DC "+*dc_component.getName()+" not atached to any Computing")) // issues = append(issues, errors.New("DC "+*dc_component.getName()+" not attached to any Computing"))
} // }
} // }
for dcID, data_component := range returned_wf.Data { // for dcID, data_component := range returned_wf.Data {
// if rID doesn't exist in the list, it means that it's not used // // if rID doesn't exist in the list, it means that it's not used
if _, ok := dataslist[dcID]; !ok { // if _, ok := dataslist[dcID]; !ok {
issues = append(issues, errors.New("Data "+*data_component.getName()+" not atached to any Computing")) // issues = append(issues, errors.New("Data "+*data_component.getName()+" not attached to any Computing"))
} // }
} // }
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
// // // //
@ -819,57 +833,116 @@ func (ws Workspace) ConsumeMxGraphModel(xmlmodel MxGraphModel) (returned_wf *Wor
return return
} }
func (w Workspace) CreateLinks(returned_wf *Workflow, links []MxLink, issues []error) []error { func (w *Workflow) CreateLinks(links []MxLink, issues []error) []error {
for _, link := range links { for _, link := range links {
sourceObj := returned_wf.GetResource(&link.Source) if (len(link.Source) > 0 && len(link.Target) > 0){
targetObj := returned_wf.GetResource(&link.Target) sourceObj := w.GetResource(&link.Source)
targetObj := w.GetResource(&link.Target)
link_object := NewLink(sourceObj,link.Source, targetObj, link.Target) link_object := NewLink(sourceObj,link.Source, targetObj, link.Target)
returned_wf.AddLinkToWorkflow(link_object,link.ID) w.AddLinkToWorkflow(link_object,link.ID)
} else {
if sourceObj == nil || targetObj == nil { issues = append(issues, w.processLinkErrors(link))
if sourceObj == nil && targetObj == nil {
issues = append(issues, errors.New("Arrow "+link.ID +" is alone"))
} else if sourceObj == nil {
issues = append(issues, errors.New("Arrow ("+link.ID+") to "+*targetObj.getName()+" without parent"))
} else {
issues = append(issues, errors.New("Arrow "+link.ID+" from "+*sourceObj.getName()+" without target"))
}
} }
// if sourceObj.getRtype() == rtype.DATACENTER || targetObj.getRtype() == rtype.DATACENTER {
// var datacenter, datacenterLinked *string
// if sourceObj.getRtype() == rtype.DATACENTER {
// datacenter = &link.Source
// datacenterLinked = &link.Target
// } else {
// datacenter = &link.Target
// datacenterLinked = &link.Source
// }
// switch returned_wf.GetResource(datacenterLinked).getRtype() {
// case rtype.COMPUTING:
// computingObj := returned_wf.GetResource(datacenterLinked).(*ComputingObject)
// computingObj.DataCenterID = *datacenter
// returned_wf.UpdateObj(computingObj, *datacenterLinked)
// }
// } else {
// targetObj.addLink(INPUT, *link.Source)
// returned_wf.UpdateObj(targetObj, *link.Target)
// sourceObj.addLink(OUTPUT, *link.Target)
// returned_wf.UpdateObj(sourceObj, *link.Source)
// }
} }
return issues return issues
} }
func (w *Workflow) processLinkErrors(link MxLink) (issue error) {
if len(link.Source) == 0 && len(link.Target) == 0 {
issue = errors.New("Arrow "+link.ID+" is alone")
} else if len(link.Source) == 0{
targetObj := w.GetResource(&link.Target)
issue = errors.New("Arrow ("+link.ID+") to "+*targetObj.getName()+" without parent")
} else {
sourceObj := w.GetResource(&link.Source)
issue = errors.New("Arrow "+link.ID+" from "+*sourceObj.getName()+" without target")
}
return issue
}
func (w *Workflow) CheckLinks(issues []error) []error {
// Check that storage components have a valid link
for id, storage := range w.Storage {
if(!w.IsComponentSrc(id) && !w.IsComponentDst(id)){
issues = append(issues, errors.New("Storage "+*storage.getName()+" without compatible inputs and outputs"))
}
}
// Check that data components are linked to a computing component
for id, data := range w.Data {
if(!w.HasLinkageToComputing(id)){
issues = append(issues, errors.New("Data "+*data.getName()+" not attached to any Computing"))
}
}
// Check that DC is linked to a computing component
for id, dc:= range w.Datacenter {
if(!w.HasLinkageToComputing(id)){
issues = append(issues, errors.New("Datacenter "+*dc.getName()+" not attached to any Computing"))
}
}
// Check that all data computing components are linked to a DC
for id,comp:= range w.Computing {
if(!w.HasLinkageToDC(id)){
issues = append(issues, errors.New("Computing "+*comp.getName()+" not attached to any datacenter"))
}
}
return issues
}
func (w *Workflow) IsComponentSrc(id string) bool {
for _, link := range w.Links{
if(link.Source == id && link.Source != ""){
return true
}
}
return false
}
func (w *Workflow) IsComponentDst(id string) bool {
for _, link := range w.Links{
if(link.Destination == id && link.Source != ""){
return true
}
}
return false
}
func (w *Workflow) HasLinkageToComputing(id string) bool {
for idComputing, _ := range w.Computing {
if( (w.IsComponentSrc(id) && w.IsComponentDst(idComputing)) || (w.IsComponentSrc(idComputing) && w.IsComponentDst(id))){
return true
}
}
return false
}
func (w *Workflow) HasLinkageToDC(id string) bool {
for _, link := range w.Links{
if(link.Source == id && link.DCLink){
return true
}
}
return false
}
func sumExecutionReqs(exeqReq ...ExecutionRequirementsModel) (ret ExecutionRequirementsModel) { func sumExecutionReqs(exeqReq ...ExecutionRequirementsModel) (ret ExecutionRequirementsModel) {
for _, v := range exeqReq { for _, v := range exeqReq {
ret.CPUs += v.CPUs ret.CPUs += v.CPUs