// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package db import ( "context" "database/sql" "fmt" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/printutils" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" ) type DBJointModelDispatcher struct { DBModelDispatcher } func NewJointModelHandler(manager IJointModelManager) *DBJointModelDispatcher { // registerModelManager(manager) return &DBJointModelDispatcher{DBModelDispatcher: DBModelDispatcher{manager: manager}} } func (dispatcher *DBJointModelDispatcher) JointModelManager() IJointModelManager { return dispatcher.manager.(IJointModelManager) } func (dispatcher *DBJointModelDispatcher) MasterKeywordPlural() string { jointManager := dispatcher.JointModelManager() if jointManager == nil { log.Fatalf("nil jointModelManager") } return jointManager.GetMasterManager().KeywordPlural() } func (dispatcher *DBJointModelDispatcher) SlaveKeywordPlural() string { jointManager := dispatcher.JointModelManager() if jointManager == nil { log.Fatalf("nil jointModelManager") } return jointManager.GetSlaveManager().KeywordPlural() } func (dispatcher *DBJointModelDispatcher) ListMasterDescendent(ctx context.Context, idStr string, query jsonutils.JSONObject) (*printutils.ListResult, error) { //log.Debugf("ListMasterDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr) userCred := fetchUserCredential(ctx) var queryDict *jsonutils.JSONDict if query != nil { queryDict, _ = query.(*jsonutils.JSONDict) if queryDict == nil { return nil, fmt.Errorf("fail to convert query to dict") } } manager := dispatcher.manager.GetImmutableInstance(ctx, userCred, query).(IJointModelManager) ctx = manager.PrepareQueryContext(ctx, userCred, query) model, err := fetchItem(manager.GetMasterManager(), ctx, userCred, idStr, query) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(manager.GetMasterManager().Keyword(), idStr) } else { return nil, err } } queryDict.Add(jsonutils.NewString(model.GetId()), fmt.Sprintf("%s_id", manager.GetMasterManager().Keyword())) queryDict.Add(jsonutils.NewString(model.GetId()), manager.GetMasterFieldName()) if len(manager.GetMasterManager().Alias()) > 0 { queryDict.Add(jsonutils.NewString(model.GetId()), fmt.Sprintf("%s_id", manager.GetMasterManager().Alias())) } return _listJoint(manager, ctx, userCred, model.(IStandaloneModel), queryDict) } func (dispatcher *DBJointModelDispatcher) ListSlaveDescendent(ctx context.Context, idStr string, query jsonutils.JSONObject) (*printutils.ListResult, error) { //log.Debugf("ListSlaveDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr) userCred := fetchUserCredential(ctx) var queryDict *jsonutils.JSONDict if query != nil { queryDict, _ = query.(*jsonutils.JSONDict) if queryDict == nil { return nil, fmt.Errorf("fail to convert query to dict") } } manager := dispatcher.manager.GetImmutableInstance(ctx, userCred, query).(IJointModelManager) ctx = manager.PrepareQueryContext(ctx, userCred, query) model, err := fetchItem(manager.GetSlaveManager(), ctx, userCred, idStr, query) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(manager.GetSlaveManager().Keyword(), idStr) } else { return nil, httperrors.NewGeneralError(err) } } queryDict.Add(jsonutils.NewString(model.GetId()), fmt.Sprintf("%s_id", manager.GetSlaveManager().Keyword())) queryDict.Add(jsonutils.NewString(model.GetId()), manager.GetSlaveFieldName()) if len(manager.GetSlaveManager().Alias()) > 0 { queryDict.Add(jsonutils.NewString(model.GetId()), fmt.Sprintf("%s_id", manager.GetSlaveManager().Alias())) } return _listJoint(manager, ctx, userCred, model.(IStandaloneModel), queryDict) } func _listJoint(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, ctxModel IStandaloneModel, queryDict jsonutils.JSONObject) (*printutils.ListResult, error) { items, err := ListItems(manager, ctx, userCred, queryDict, nil) if err != nil { log.Errorf("Fail to list items: %s", err) return nil, httperrors.NewGeneralError(err) } return items, nil } func fetchJointItem(manager IJointModelManager, ctx context.Context, userCred mcclient.TokenCredential, id1 string, id2 string, query jsonutils.JSONObject) (IStandaloneModel, IStandaloneModel, IJointModel, error) { master, err := fetchItem(manager.GetMasterManager(), ctx, userCred, id1, query) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, nil, nil, httperrors.NewResourceNotFoundError2(manager.GetMasterManager().Keyword(), id1) } else { return nil, nil, nil, httperrors.NewGeneralError(err) } } slave, err := fetchItem(manager.GetSlaveManager(), ctx, userCred, id2, query) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, nil, nil, httperrors.NewResourceNotFoundError2(manager.GetSlaveManager().Keyword(), id2) } else { return nil, nil, nil, httperrors.NewGeneralError(err) } } item, err := FetchJointByIds(manager, master.GetId(), slave.GetId(), query) if err != nil { return nil, nil, nil, err } return master.(IStandaloneModel), slave.(IStandaloneModel), item, nil } func (dispatcher *DBJointModelDispatcher) Get(ctx context.Context, id1 string, id2 string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { userCred := fetchUserCredential(ctx) manager := dispatcher.manager.GetImmutableInstance(ctx, userCred, query).(IJointModelManager) ctx = manager.PrepareQueryContext(ctx, userCred, query) _, _, item, err := fetchJointItem(manager, ctx, userCred, id1, id2, query) if err == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), id1+"-"+id2) } else if err != nil { return nil, httperrors.NewGeneralError(err) } err = isJointObjectRbacAllowed(ctx, item, userCred, policy.PolicyActionGet) if err != nil { return nil, err } return getItemDetails(manager, item, ctx, userCred, query) } func attachItems( dispatcher *DBJointModelDispatcher, master IStandaloneModel, slave IStandaloneModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject, ) (jsonutils.JSONObject, error) { err := isObjectRbacAllowed(ctx, master, userCred, policy.PolicyActionPerform, "attach") if err != nil { return nil, err } err = isObjectRbacAllowed(ctx, slave, userCred, policy.PolicyActionPerform, "attach") if err != nil { return nil, err } // ownerProjId, err := fetchOwnerId(ctx, dispatcher.JointModelManager(), userCred, data) dataDict, ok := data.(*jsonutils.JSONDict) if !ok { return nil, fmt.Errorf("body not a json dict") } dataDict.Add(jsonutils.NewString(master.GetId()), fmt.Sprintf("%s_id", dispatcher.JointModelManager().GetMasterManager().Keyword())) if len(dispatcher.JointModelManager().GetMasterManager().Alias()) > 0 { dataDict.Add(jsonutils.NewString(master.GetId()), fmt.Sprintf("%s_id", dispatcher.JointModelManager().GetMasterManager().Alias())) } dataDict.Add(jsonutils.NewString(slave.GetId()), fmt.Sprintf("%s_id", dispatcher.JointModelManager().GetSlaveManager().Keyword())) if len(dispatcher.JointModelManager().GetSlaveManager().Alias()) > 0 { dataDict.Add(jsonutils.NewString(slave.GetId()), fmt.Sprintf("%s_id", dispatcher.JointModelManager().GetSlaveManager().Alias())) } item, err := doCreateItem(dispatcher.JointModelManager(), ctx, userCred, nil, query, data) if err != nil { return nil, httperrors.NewGeneralError(err) } item.PostCreate(ctx, userCred, nil, query, data) if err := dispatcher.JointModelManager().GetExtraHook().AfterPostCreate(ctx, userCred, item.GetOwnerId(), item, query, data); err != nil { logclient.AddActionLogWithContext(ctx, item, logclient.ACT_POST_CREATE_HOOK, err, userCred, false) } OpsLog.LogAttachEvent(ctx, master, slave, userCred, jsonutils.Marshal(item)) dispatcher.manager.OnCreateComplete(ctx, []IModel{item}, userCred, nil, query, []jsonutils.JSONObject{data}) return getItemDetails(dispatcher.JointModelManager(), item, ctx, userCred, query) } func (dispatcher *DBJointModelDispatcher) Attach(ctx context.Context, id1 string, id2 string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { userCred := fetchUserCredential(ctx) master, err := fetchItem(dispatcher.JointModelManager().GetMasterManager(), ctx, userCred, id1, query) if err != nil { if err == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(dispatcher.JointModelManager().GetMasterManager().Keyword(), id1) } else { return nil, httperrors.NewGeneralError(err) } } slave, err := fetchItem(dispatcher.JointModelManager().GetSlaveManager(), ctx, userCred, id2, query) if err != nil { if err == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(dispatcher.JointModelManager().GetSlaveManager().Keyword(), id2) } else { return nil, httperrors.NewGeneralError(err) } } _, _, joinItem, err := fetchJointItem(dispatcher.JointModelManager(), ctx, userCred, master.GetId(), slave.GetId(), query) if err != nil && err != sql.ErrNoRows { return nil, httperrors.NewGeneralError(err) } if joinItem != nil { return nil, httperrors.NewNotAcceptableError("Object %s %s has attached %s %s", master.KeywordPlural(), master.GetId(), slave.KeywordPlural(), slave.GetId()) } lockman.LockJointObject(ctx, master, slave) defer lockman.ReleaseJointObject(ctx, master, slave) resp, err := attachItems(dispatcher, master.(IStandaloneModel), slave.(IStandaloneModel), ctx, userCred, query, data) if err == nil { CallCustomizeNotifyHook(ctx, userCred, ACT_ATTACH, master, slave.GetShortDesc(ctx)) } return resp, err } func (dispatcher *DBJointModelDispatcher) Update(ctx context.Context, id1 string, id2 string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { // 获取用户信息 userCred := fetchUserCredential(ctx) manager := dispatcher.manager.GetMutableInstance(ctx, userCred, query, data) // 获取对象与关联表(such as guestdisks_tbl) master, slave, item, err := fetchJointItem(dispatcher.JointModelManager(), ctx, userCred, id1, id2, query) if err == sql.ErrNoRows { if jsonutils.QueryBoolean(query, "auto_create", false) { queryDict := query.(*jsonutils.JSONDict) queryDict.Remove("auto_create") return dispatcher.Attach(ctx, id1, id2, query, data) } return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), id1+"-"+id2) } else if err != nil { return nil, httperrors.NewGeneralError(err) } // 判断权限 err = isJointObjectRbacAllowed(ctx, item, userCred, policy.PolicyActionUpdate) if err != nil { return nil, err } // 锁住实例与关联表 lockman.LockJointObject(ctx, master, slave) defer lockman.ReleaseJointObject(ctx, master, slave) return updateItem(dispatcher.JointModelManager(), item, ctx, userCred, query, data) } func (dispatcher *DBJointModelDispatcher) Detach(ctx context.Context, id1 string, id2 string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { userCred := fetchUserCredential(ctx) manager := dispatcher.manager.GetMutableInstance(ctx, userCred, query, data) master, slave, item, err := fetchJointItem(dispatcher.JointModelManager(), ctx, userCred, id1, id2, query) if err == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), id1+"-"+id2) } else if err != nil { return nil, httperrors.NewGeneralError(err) } err = isObjectRbacAllowed(ctx, master, userCred, policy.PolicyActionPerform, "detach") if err != nil { return nil, err } err = isObjectRbacAllowed(ctx, slave, userCred, policy.PolicyActionPerform, "detach") if err != nil { return nil, err } lockman.LockJointObject(ctx, master, slave) defer lockman.ReleaseJointObject(ctx, master, slave) obj, err := deleteItem(dispatcher.JointModelManager(), item, ctx, userCred, query, data) if err == nil { CallCustomizeNotifyHook(ctx, userCred, ACT_DETACH, master, slave.GetShortDesc(ctx)) OpsLog.LogDetachEvent(ctx, JointMaster(item), JointSlave(item), userCred, jsonutils.Marshal(item)) } return obj, err } func DetachJoint(ctx context.Context, userCred mcclient.TokenCredential, item IJointModel) error { err := ValidateDeleteCondition(item, ctx, nil) if err != nil { return err } err = item.Delete(ctx, userCred) if err == nil { OpsLog.LogDetachEvent(ctx, JointMaster(item), JointSlave(item), userCred, item.GetShortDesc(ctx)) } if err := item.GetModelManager().GetExtraHook().AfterPostDelete(ctx, userCred, item, nil); err != nil { logclient.AddActionLogWithContext(ctx, item, logclient.ACT_POST_DELETE_HOOK, err, userCred, false) } return err }