diff --git a/models/meshmodel/core/types/types.go b/models/meshmodel/core/types/types.go index 0c928bbc..eec293ab 100644 --- a/models/meshmodel/core/types/types.go +++ b/models/meshmodel/core/types/types.go @@ -6,6 +6,7 @@ const ( ComponentDefinition CapabilityType = "component" PolicyDefinition CapabilityType = "policy" RelationshipDefinition CapabilityType = "relationship" + Model CapabilityType = "model" ) // Each entity will have it's own Filter implementation via which it exposes the nobs and dials to fetch entities diff --git a/models/meshmodel/core/v1alpha1/component.go b/models/meshmodel/core/v1alpha1/component.go index 782f5d64..f31bde5d 100644 --- a/models/meshmodel/core/v1alpha1/component.go +++ b/models/meshmodel/core/v1alpha1/component.go @@ -69,19 +69,20 @@ func emptySchemaCheck(schema string) (valid bool) { valid = true return } -func CreateComponent(db *database.Handler, c ComponentDefinition) (uuid.UUID, error) { +func CreateComponent(db *database.Handler, c ComponentDefinition) (uuid.UUID, uuid.UUID, error) { c.ID = uuid.New() mid, err := CreateModel(db, c.Model) if err != nil { - return uuid.UUID{}, err + return uuid.UUID{}, uuid.UUID{}, err } + if !emptySchemaCheck(c.Schema) { c.Metadata["hasInvalidSchema"] = true } cdb := c.GetComponentDefinitionDB() cdb.ModelID = mid err = db.Create(&cdb).Error - return c.ID, err + return c.ID, mid, err } func GetMeshModelComponents(db *database.Handler, f ComponentFilter) (c []ComponentDefinition, count int64, unique int) { type componentDefinitionWithModel struct { diff --git a/models/meshmodel/core/v1alpha1/models.go b/models/meshmodel/core/v1alpha1/models.go index 5af1da5a..91c6e04c 100644 --- a/models/meshmodel/core/v1alpha1/models.go +++ b/models/meshmodel/core/v1alpha1/models.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/layer5io/meshkit/database" + "github.com/layer5io/meshkit/models/meshmodel/core/types" "gorm.io/gorm" ) @@ -53,6 +54,13 @@ type ModelDB struct { Metadata []byte `json:"modelMetadata" gorm:"modelMetadata"` } +func (m Model) Type() types.CapabilityType { + return types.Model +} +func (m Model) GetID() uuid.UUID { + return m.ID +} + func CreateModel(db *database.Handler, cmodel Model) (uuid.UUID, error) { byt, err := json.Marshal(cmodel) if err != nil { diff --git a/models/meshmodel/core/v1alpha1/policy.go b/models/meshmodel/core/v1alpha1/policy.go index f7fd8bbe..0791b705 100644 --- a/models/meshmodel/core/v1alpha1/policy.go +++ b/models/meshmodel/core/v1alpha1/policy.go @@ -96,19 +96,19 @@ func (pdb *PolicyDefinitionDB) GetPolicyDefinition(m Model) (p PolicyDefinition) return } -func CreatePolicy(db *database.Handler, p PolicyDefinition) (uuid.UUID, error) { +func CreatePolicy(db *database.Handler, p PolicyDefinition) (uuid.UUID, uuid.UUID, error) { p.ID = uuid.New() mid, err := CreateModel(db, p.Model) if err != nil { - return uuid.UUID{}, err + return uuid.UUID{}, uuid.UUID{}, err } pdb := p.GetPolicyDefinitionDB() pdb.ModelID = mid err = db.Create(&pdb).Error if err != nil { - return uuid.UUID{}, err + return uuid.UUID{}, uuid.UUID{}, err } - return pdb.ID, nil + return pdb.ID, mid, nil } func (p *PolicyDefinition) GetPolicyDefinitionDB() (pdb PolicyDefinitionDB) { diff --git a/models/meshmodel/core/v1alpha1/relationship.go b/models/meshmodel/core/v1alpha1/relationship.go index 0f364647..747595ba 100644 --- a/models/meshmodel/core/v1alpha1/relationship.go +++ b/models/meshmodel/core/v1alpha1/relationship.go @@ -137,19 +137,19 @@ func (r RelationshipDefinition) GetID() uuid.UUID { return r.ID } -func CreateRelationship(db *database.Handler, r RelationshipDefinition) (uuid.UUID, error) { +func CreateRelationship(db *database.Handler, r RelationshipDefinition) (uuid.UUID, uuid.UUID, error) { r.ID = uuid.New() mid, err := CreateModel(db, r.Model) if err != nil { - return uuid.UUID{}, err + return uuid.UUID{}, uuid.UUID{}, err } rdb := r.GetRelationshipDefinitionDB() rdb.ModelID = mid err = db.Create(&rdb).Error if err != nil { - return uuid.UUID{}, err + return uuid.UUID{}, uuid.UUID{}, err } - return r.ID, err + return r.ID, mid, err } func (r *RelationshipDefinition) GetRelationshipDefinitionDB() (rdb RelationshipDefinitionDB) { diff --git a/models/meshmodel/registry/host.go b/models/meshmodel/registry/host.go index 1a1abab7..cbc1350d 100644 --- a/models/meshmodel/registry/host.go +++ b/models/meshmodel/registry/host.go @@ -1,8 +1,9 @@ package registry import ( - "errors" + "encoding/json" "fmt" + "sync" "time" "github.com/google/uuid" @@ -12,6 +13,8 @@ import ( "gorm.io/gorm" ) +var hostCreationLock sync.Mutex //Each entity will perform a check and if the host already doesn't exist, it will create a host. This lock will make sure that there are no race conditions. + type Host struct { ID uuid.UUID `json:"-"` Hostname string @@ -23,9 +26,31 @@ type Host struct { } func createHost(db *database.Handler, h Host) (uuid.UUID, error) { - h.ID = uuid.New() - err := db.Create(&h).Error - return h.ID, err + byt, err := json.Marshal(h) + if err != nil { + return uuid.UUID{}, err + } + hID := uuid.NewSHA1(uuid.UUID{}, byt) + var host Host + hostCreationLock.Lock() + defer hostCreationLock.Unlock() + err = db.First(&host, "id = ?", hID).Error // check if the host already exists + if err != nil && err != gorm.ErrRecordNotFound { + return uuid.UUID{}, err + } + + // if not exists then create a new host and return the id + if err == gorm.ErrRecordNotFound { + h.ID = hID + err = db.Create(&h).Error + if err != nil { + return uuid.UUID{}, err + } + return h.ID, nil + } + + // else return the id of the existing host + return host.ID, nil } func (h *Host) AfterFind(tx *gorm.DB) error { @@ -34,8 +59,8 @@ func (h *Host) AfterFind(tx *gorm.DB) error { h.IHost = ArtifactHub{} case "kubernetes": h.IHost = Kubernetes{} - default: - return ErrUnknownHost(errors.New("unable to find compatible host for the component")) + default: // do nothing if the host is not pre-unknown. Currently adapters fall into this case. + return nil } return nil } diff --git a/models/meshmodel/registry/registry.go b/models/meshmodel/registry/registry.go index c57deef3..cd4b6cbd 100644 --- a/models/meshmodel/registry/registry.go +++ b/models/meshmodel/registry/registry.go @@ -1,6 +1,7 @@ package registry import ( + "encoding/json" "fmt" "strings" "time" @@ -11,6 +12,7 @@ import ( "github.com/layer5io/meshkit/models/meshmodel/core/v1alpha1" "golang.org/x/text/cases" "golang.org/x/text/language" + "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -47,6 +49,37 @@ type RegistryManager struct { db *database.Handler //This database handler will be used to perform queries inside the database } +// Registers models into registries table. +func registerModel(db *database.Handler, regID, modelID uuid.UUID) error { + entity := Registry{ + RegistrantID: regID, + Entity: modelID, + Type: types.Model, + } + + byt, err := json.Marshal(entity) + if err != nil { + return err + } + + entityID := uuid.NewSHA1(uuid.UUID{}, byt) + var reg Registry + err = db.First(®, "id = ?", entityID).Error + if err != nil && err != gorm.ErrRecordNotFound { + return err + } + + if err == gorm.ErrRecordNotFound { + entity.ID = entityID + err = db.Create(&entity).Error + if err != nil { + return err + } + } + + return nil +} + // NewRegistryManager initializes the registry manager by creating appropriate tables. // Any new entities that are added to the registry should be migrated here into the database func NewRegistryManager(db *database.Handler) (*RegistryManager, error) { @@ -86,14 +119,22 @@ func (rm *RegistryManager) RegisterEntity(h Host, en Entity) error { if entity.Schema == "" { //For components with an empty schema, exit quietly return nil } - componentID, err := v1alpha1.CreateComponent(rm.db, entity) + + registrantID, err := createHost(rm.db, h) if err != nil { return err } - registrantID, err := createHost(rm.db, h) + + componentID, modelID, err := v1alpha1.CreateComponent(rm.db, entity) if err != nil { return err } + + err = registerModel(rm.db, registrantID, modelID) + if err != nil { + return err + } + entry := Registry{ ID: uuid.New(), RegistrantID: registrantID, @@ -104,14 +145,22 @@ func (rm *RegistryManager) RegisterEntity(h Host, en Entity) error { } return rm.db.Create(&entry).Error case v1alpha1.RelationshipDefinition: - relationshipID, err := v1alpha1.CreateRelationship(rm.db, entity) + + registrantID, err := createHost(rm.db, h) if err != nil { return err } - registrantID, err := createHost(rm.db, h) + + relationshipID, modelID, err := v1alpha1.CreateRelationship(rm.db, entity) + if err != nil { + return err + } + + err = registerModel(rm.db, registrantID, modelID) if err != nil { return err } + entry := Registry{ ID: uuid.New(), RegistrantID: registrantID, @@ -123,14 +172,21 @@ func (rm *RegistryManager) RegisterEntity(h Host, en Entity) error { return rm.db.Create(&entry).Error //Add logic for Policies and other entities below case v1alpha1.PolicyDefinition: - policyID, err := v1alpha1.CreatePolicy(rm.db, entity) + registrantID, err := createHost(rm.db, h) if err != nil { return err } - registrantID, err := createHost(rm.db, h) + + policyID, modelID, err := v1alpha1.CreatePolicy(rm.db, entity) if err != nil { return err } + + err = registerModel(rm.db, registrantID, modelID) + if err != nil { + return err + } + entry := Registry{ ID: uuid.New(), RegistrantID: registrantID, @@ -194,7 +250,7 @@ func (rm *RegistryManager) GetModels(db *database.Handler, f types.Filter) ([]v1 var modelWithCategoriess []modelWithCategories finder := db.Model(&v1alpha1.ModelDB{}). Select("model_dbs.*, category_dbs.*"). - Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") // + Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") // total count before pagination var count int64 @@ -247,7 +303,12 @@ func (rm *RegistryManager) GetModels(db *database.Handler, f types.Filter) ([]v1 } for _, modelDB := range modelWithCategoriess { - m = append(m, modelDB.ModelDB.GetModel(modelDB.GetCategory(db))) + model := modelDB.ModelDB.GetModel(modelDB.GetCategory(db)) + host := rm.GetRegistrant(model) + model.HostID = host.ID + model.HostName = host.Hostname + model.DisplayHostName = host.Hostname + m = append(m, model) } return m, count, countUniqueModels(modelWithCategoriess) }