func (tl *transactionEventLogger) Delete(s *schema.Schema, resourceID interface{}) error { resource, err := tl.Fetch(s, transaction.IDFilter(resourceID)) if err != nil { return err } configVersion := int64(0) if resource.Schema().StateVersioning() { state, err := tl.StateFetch(s, transaction.IDFilter(resourceID)) if err != nil { return err } configVersion = state.ConfigVersion + 1 } err = tl.Transaction.Delete(s, resourceID) if err != nil { return err } return tl.logEvent("delete", resource, configVersion) }
// DeleteResource deletes the resource specified by the schema and ID func DeleteResource(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, resourceID string, ) error { context["id"] = resourceID environmentManager := extension.GetManager() environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) if !ok { return fmt.Errorf("No environment for schema") } auth := context["auth"].(schema.Authorization) policy, err := loadPolicy(context, "delete", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth) if err != nil { return err } context["policy"] = policy preTransaction, err := dataStore.Begin() if err != nil { return fmt.Errorf("cannot create transaction: %v", err) } tenantIDs := policy.GetTenantIDFilter(schema.ActionDelete, auth.TenantID()) filter := transaction.IDFilter(resourceID) if tenantIDs != nil { filter["tenant_id"] = tenantIDs } resource, fetchErr := preTransaction.Fetch(resourceSchema, filter) preTransaction.Close() if resource != nil { context["resource"] = resource.Data() } if err := extension.HandleEvent(context, environment, "pre_delete"); err != nil { return err } if fetchErr != nil { return ResourceError{err, "", NotFound} } if err := InTransaction( context, dataStore, transaction.GetIsolationLevel(resourceSchema, schema.ActionDelete), func() error { return DeleteResourceInTransaction(context, resourceSchema, resourceID) }, ); err != nil { return err } if err := extension.HandleEvent(context, environment, "post_delete"); err != nil { return err } return nil }
//CopyDBResources copies resources from input database to output database func CopyDBResources(input, output DB, overrideExisting bool) error { schemaManager := schema.GetManager() schemas := schemaManager.OrderedSchemas() if len(schemas) == 0 { return fmt.Errorf(noSchemasInManagerError) } itx, err := input.Begin() if err != nil { return err } defer itx.Close() otx, err := output.Begin() if err != nil { return err } defer otx.Close() for _, s := range schemas { if s.IsAbstract() { continue } log.Info("Populating resources for schema %s", s.ID) resources, _, err := itx.List(s, nil, nil) if err != nil { return err } for _, resource := range resources { log.Info("Creating resource %s", resource.ID()) destResource, _ := otx.Fetch(s, transaction.IDFilter(resource.ID())) if destResource == nil { resource.PopulateDefaults() err := otx.Create(resource) if err != nil { return err } } else if overrideExisting { err := otx.Update(resource) if err != nil { return err } } } } err = itx.Commit() if err != nil { return err } return otx.Commit() }
func (tl *transactionEventLogger) Update(resource *schema.Resource) error { err := tl.Transaction.Update(resource) if err != nil { return err } if !resource.Schema().StateVersioning() { return tl.logEvent("update", resource, 0) } state, err := tl.StateFetch(resource.Schema(), transaction.IDFilter(resource.ID())) if err != nil { return err } return tl.logEvent("update", resource, state.ConfigVersion) }
//DBGet get resource from a db. func DBGet(tx transaction.Transaction, schemaID string, id string, tenantID string) (map[string]interface{}, error) { manager := schema.GetManager() schemaObj, ok := manager.Schema(schemaID) if !ok { return nil, fmt.Errorf("Schema %s not found", schemaID) } filter := transaction.IDFilter(id) if tenantID != "" { filter["tenant_id"] = tenantID } resp, err := tx.Fetch(schemaObj, filter) if err != nil { return nil, err } return resp.Data(), err }
//GohanDbFetch gets resource from database func GohanDbFetch(tx transaction.Transaction, schemaID, ID, tenantID string) (*schema.Resource, error) { schema, err := getSchema(schemaID) if err != nil { return nil, err } filter := transaction.IDFilter(ID) if tenantID != "" { filter["tenant_id"] = tenantID } resp, err := tx.Fetch(schema, filter) if err != nil { return nil, fmt.Errorf("Error during gohan_db_fetch: %s", err.Error()) } return resp, nil }
//DeleteResourceInTransaction deletes resources in a transaction func DeleteResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string) error { mainTransaction := context["transaction"].(transaction.Transaction) environmentManager := extension.GetManager() environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) if !ok { return fmt.Errorf("No environment for schema") } auth := context["auth"].(schema.Authorization) policy := context["policy"].(*schema.Policy) tenantIDs := policy.GetTenantIDFilter(schema.ActionDelete, auth.TenantID()) filter := transaction.IDFilter(resourceID) if tenantIDs != nil { filter["tenant_id"] = tenantIDs } resource, err := mainTransaction.Fetch(resourceSchema, filter) log.Debug("%s %s", resource, err) if err != nil { return err } if resource != nil { context["resource"] = resource.Data() } // apply property filter err = policy.ApplyPropertyConditionFilter(schema.ActionUpdate, resource.Data(), nil) if err != nil { return ResourceError{err, "", Unauthorized} } if err := extension.HandleEvent(context, environment, "pre_delete_in_transaction"); err != nil { return err } err = mainTransaction.Delete(resourceSchema, resourceID) if err != nil { return ResourceError{err, "", DeleteFailed} } if err := extension.HandleEvent(context, environment, "post_delete_in_transaction"); err != nil { return err } return nil }
//GohanDbStateFetch gets resource's state from database func GohanDbStateFetch(tx transaction.Transaction, schemaID, ID, tenantID string) (map[string]interface{}, error) { schema, err := getSchema(schemaID) if err != nil { return map[string]interface{}{}, err } filter := transaction.IDFilter(ID) if tenantID != "" { filter["tenant_id"] = tenantID } resp, err := tx.StateFetch(schema, filter) if err != nil { return map[string]interface{}{}, fmt.Errorf("Error during gohan_db_state_fetch: %s", err.Error()) } data := map[string]interface{}{ "config_version": resp.ConfigVersion, "state_version": resp.StateVersion, "error": resp.Error, "state": resp.State, "monitoring": resp.Monitoring, } return data, nil }
// CreateOrUpdateResource updates resource if it existed and otherwise creates it and returns true. func CreateOrUpdateResource( context middleware.Context, dataStore db.DB, identityService middleware.IdentityService, resourceSchema *schema.Schema, resourceID string, dataMap map[string]interface{}, ) (bool, error) { auth := context["auth"].(schema.Authorization) //LoadPolicy policy, err := loadPolicy(context, "update", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth) if err != nil { return false, err } preTransaction, err := dataStore.Begin() if err != nil { return false, fmt.Errorf("cannot create transaction: %v", err) } tenantIDs := policy.GetTenantIDFilter(schema.ActionUpdate, auth.TenantID()) filter := transaction.IDFilter(resourceID) if tenantIDs != nil { filter["tenant_id"] = tenantIDs } _, fetchErr := preTransaction.Fetch(resourceSchema, filter) preTransaction.Close() if fetchErr != nil { dataMap["id"] = resourceID if err := CreateResource(context, dataStore, identityService, resourceSchema, dataMap); err != nil { return false, err } return true, err } return false, UpdateResource(context, dataStore, identityService, resourceSchema, resourceID, dataMap) }
//GetSingleResourceInTransaction get resource in single transaction func GetSingleResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string, tenantIDs []string) (err error) { mainTransaction := context["transaction"].(transaction.Transaction) environmentManager := extension.GetManager() environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) if !ok { return fmt.Errorf("no environment for schema") } if err := extension.HandleEvent(context, environment, "pre_show_in_transaction"); err != nil { return err } if rawResponse, ok := context["response"]; ok { if _, ok := rawResponse.(map[string]interface{}); ok { return nil } return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) } filter := transaction.IDFilter(resourceID) if tenantIDs != nil { filter["tenant_id"] = tenantIDs } object, err := mainTransaction.Fetch(resourceSchema, filter) if err != nil || object == nil { return ResourceError{err, "", NotFound} } response := map[string]interface{}{} response[resourceSchema.Singular] = object.Data() context["response"] = response if err := extension.HandleEvent(context, environment, "post_show_in_transaction"); err != nil { return err } return }
// UpdateResourceInTransaction updates resource in db in transaction func UpdateResourceInTransaction( context middleware.Context, resourceSchema *schema.Schema, resourceID string, dataMap map[string]interface{}, tenantIDs []string) error { manager := schema.GetManager() mainTransaction := context["transaction"].(transaction.Transaction) environmentManager := extension.GetManager() environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) if !ok { return fmt.Errorf("No environment for schema") } filter := transaction.IDFilter(resourceID) if tenantIDs != nil { filter["tenant_id"] = tenantIDs } resource, err := mainTransaction.Fetch( resourceSchema, filter) if err != nil { return ResourceError{err, err.Error(), WrongQuery} } policy := context["policy"].(*schema.Policy) // apply property filter err = policy.ApplyPropertyConditionFilter(schema.ActionUpdate, resource.Data(), dataMap) if err != nil { return ResourceError{err, "", Unauthorized} } err = resource.Update(dataMap) if err != nil { return ResourceError{err, err.Error(), WrongData} } context["resource"] = resource.Data() if err := extension.HandleEvent(context, environment, "pre_update_in_transaction"); err != nil { return err } dataMap, ok = context["resource"].(map[string]interface{}) if !ok { return fmt.Errorf("Resource not JSON: %s", err) } resource, err = manager.LoadResource(resourceSchema.ID, dataMap) if err != nil { return fmt.Errorf("Loading Resource failed: %s", err) } err = mainTransaction.Update(resource) if err != nil { return ResourceError{err, fmt.Sprintf("Failed to store data in database: %v", err), UpdateFailed} } response := map[string]interface{}{} response[resourceSchema.Singular] = resource.Data() context["response"] = response if err := extension.HandleEvent(context, environment, "post_update_in_transaction"); err != nil { return err } return nil }
} _, _, err := tx.List(networkSchema, filter, nil) Expect(err).To(HaveOccurred()) }) It("Shows related resources", func() { list, num, err := tx.List(serverSchema, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(num).To(Equal(uint64(1))) Expect(list).To(HaveLen(1)) Expect(list[0].Data()).To(HaveKeyWithValue("network", HaveKeyWithValue("name", networkResource1.Data()["name"]))) Expect(tx.Commit()).To(Succeed()) }) It("Fetches an existing resource", func() { networkResourceFetched, err := tx.Fetch(networkSchema, transaction.IDFilter(networkResource1.ID())) Expect(err).ToNot(HaveOccurred()) Expect(networkResourceFetched).To(util.MatchAsJSON(networkResource1)) Expect(tx.Commit()).To(Succeed()) }) It("Updates the resource properly", func() { By("Not allowing to update some fields") Expect(networkResource1.Update(map[string]interface{}{"id": "new_id"})).ToNot(Succeed()) By("Updating other fields") Expect(networkResource1.Update(map[string]interface{}{"name": "new_name"})).To(Succeed()) Expect(tx.Update(networkResource1)).To(Succeed()) Expect(tx.Commit()).To(Succeed()) })
//MonitoringUpdate updates the state in the db based on the sync event func MonitoringUpdate(response *gohan_sync.Event, server *Server) error { dataStore := server.db schemaPath := "/" + strings.TrimPrefix(response.Key, monitoringPrefix) var curSchema = schema.GetSchemaByPath(schemaPath) if curSchema == nil || !curSchema.StateVersioning() { log.Debug("Monitoring update on unexpected path '%s'", schemaPath) return nil } resourceID := curSchema.GetResourceIDFromPath(schemaPath) tx, err := dataStore.Begin() if err != nil { return err } defer tx.Close() err = tx.SetIsolationLevel(transaction.GetIsolationLevel(curSchema, MonitoringUpdateEventName)) if err != nil { return err } curResource, err := tx.Fetch(curSchema, transaction.IDFilter(resourceID)) if err != nil { return err } resourceState, err := tx.StateFetch(curSchema, transaction.IDFilter(resourceID)) if err != nil { return err } if resourceState.ConfigVersion != resourceState.StateVersion { return nil } var ok bool monitoringVersion, ok := response.Data["version"].(float64) if !ok { return fmt.Errorf("No version in monitoring information") } if resourceState.ConfigVersion != int64(monitoringVersion) { return nil } resourceState.Monitoring, ok = response.Data["monitoring"].(string) if !ok { return fmt.Errorf("No monitoring in monitoring information") } environmentManager := extension.GetManager() environment, haveEnvironment := environmentManager.GetEnvironment(curSchema.ID) context := map[string]interface{}{} context["resource"] = curResource.Data() context["schema"] = curSchema context["monitoring"] = resourceState.Monitoring context["transaction"] = tx if haveEnvironment { if err := extension.HandleEvent(context, environment, "pre_monitoring_update_in_transaction"); err != nil { return err } } err = tx.StateUpdate(curResource, &resourceState) if err != nil { return err } if haveEnvironment { if err := extension.HandleEvent(context, environment, "post_monitoring_update_in_transaction"); err != nil { return err } } return tx.Commit() }
//StateUpdate updates the state in the db based on the sync event func StateUpdate(response *gohan_sync.Event, server *Server) error { dataStore := server.db schemaPath := "/" + strings.TrimPrefix(response.Key, statePrefix) var curSchema = schema.GetSchemaByPath(schemaPath) if curSchema == nil || !curSchema.StateVersioning() { log.Debug("State update on unexpected path '%s'", schemaPath) return nil } resourceID := curSchema.GetResourceIDFromPath(schemaPath) tx, err := dataStore.Begin() if err != nil { return err } defer tx.Close() err = tx.SetIsolationLevel(transaction.GetIsolationLevel(curSchema, StateUpdateEventName)) if err != nil { return err } curResource, err := tx.Fetch(curSchema, transaction.IDFilter(resourceID)) if err != nil { return err } resourceState, err := tx.StateFetch(curSchema, transaction.IDFilter(resourceID)) if err != nil { return err } if resourceState.StateVersion == resourceState.ConfigVersion { return nil } stateVersion, ok := response.Data["version"].(float64) if !ok { return fmt.Errorf("No version in state information") } oldStateVersion := resourceState.StateVersion resourceState.StateVersion = int64(stateVersion) if resourceState.StateVersion < oldStateVersion { return nil } if newError, ok := response.Data["error"].(string); ok { resourceState.Error = newError } if newState, ok := response.Data["state"].(string); ok { resourceState.State = newState } environmentManager := extension.GetManager() environment, haveEnvironment := environmentManager.GetEnvironment(curSchema.ID) context := map[string]interface{}{} if haveEnvironment { serviceAuthorization, _ := server.keystoneIdentity.GetServiceAuthorization() context["catalog"] = serviceAuthorization.Catalog() context["auth_token"] = serviceAuthorization.AuthToken() context["resource"] = curResource.Data() context["schema"] = curSchema context["state"] = response.Data context["config_version"] = resourceState.ConfigVersion context["transaction"] = tx if err := extension.HandleEvent(context, environment, "pre_state_update_in_transaction"); err != nil { return err } } err = tx.StateUpdate(curResource, &resourceState) if err != nil { return err } if haveEnvironment { if err := extension.HandleEvent(context, environment, "post_state_update_in_transaction"); err != nil { return err } } return tx.Commit() }
It("Should work", func() { possibleEvent = gohan_sync.Event{ Action: "this is ignored here", Data: map[string]interface{}{ "version": float64(1), "error": "", "state": "Ni malvarmetas", }, Key: statePrefix + networkResource.Path(), } Expect(srv.StateUpdate(&possibleEvent, server)).To(Succeed()) tx, err := wrappedTestDB.Begin() Expect(err).ToNot(HaveOccurred()) defer tx.Close() afterState, err := tx.StateFetch(networkSchema, transaction.IDFilter(networkResource.ID())) Expect(err).ToNot(HaveOccurred()) Expect(tx.Commit()).To(Succeed()) Expect(afterState.ConfigVersion).To(Equal(int64(1))) Expect(afterState.StateVersion).To(Equal(int64(1))) Expect(afterState.State).To(Equal("Ni malvarmetas")) Expect(afterState.Error).To(Equal("")) Expect(afterState.Monitoring).To(Equal("")) }) It("Should ignore backwards updates", func() { possibleEvent = gohan_sync.Event{ Action: "this is ignored here", Data: map[string]interface{}{ "version": float64(1), "error": "",