| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- // 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"
- "runtime/debug"
- "strings"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/pkg/util/reflectutils"
- "yunion.io/x/pkg/util/stringutils"
- "yunion.io/x/pkg/util/timeutils"
- "yunion.io/x/sqlchemy"
- "yunion.io/x/onecloud/pkg/apis"
- "yunion.io/x/onecloud/pkg/appsrv"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/util/ctx"
- "yunion.io/x/onecloud/pkg/util/stringutils2"
- )
- type SOpsLogManager struct {
- SLogBaseManager
- }
- type SOpsLog struct {
- SLogBase
- ObjType string `width:"40" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
- ObjId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
- ObjName string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"required"`
- Action string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"required"`
- Notes string `charset:"utf8" list:"user" create:"optional"`
- ProjectId string `name:"tenant_id" width:"128" charset:"ascii" list:"user" create:"optional" index:"true"`
- Project string `name:"tenant" width:"128" charset:"utf8" list:"user" create:"optional"`
- ProjectDomainId string `name:"project_domain_id" default:"default" width:"128" charset:"ascii" list:"user" create:"optional"`
- ProjectDomain string `name:"project_domain" default:"Default" width:"128" charset:"utf8" list:"user" create:"optional"`
- UserId string `width:"128" charset:"ascii" list:"user" create:"required"`
- User string `width:"128" charset:"utf8" list:"user" create:"required"`
- DomainId string `width:"128" charset:"ascii" list:"user" create:"optional"`
- Domain string `width:"128" charset:"utf8" list:"user" create:"optional"`
- Roles string `width:"64" charset:"utf8" list:"user" create:"optional"`
- OpsTime time.Time `nullable:"false" list:"user" clickhouse_ttl:"6m"`
- OwnerDomainId string `name:"owner_domain_id" default:"default" width:"128" charset:"ascii" list:"user" create:"optional"`
- OwnerProjectId string `name:"owner_tenant_id" width:"128" charset:"ascii" list:"user" create:"optional"`
- }
- var OpsLog *SOpsLogManager
- var _ IModelManager = (*SOpsLogManager)(nil)
- var _ IModel = (*SOpsLog)(nil)
- var opslogQueryWorkerMan *appsrv.SWorkerManager
- var opslogWriteWorkerMan *appsrv.SWorkerManager
- func NewOpsLogManager(opslog interface{}, tblName string, keyword, keywordPlural string, timeField string, clickhouse bool) SOpsLogManager {
- return SOpsLogManager{
- SLogBaseManager: NewLogBaseManager(opslog, tblName, keyword, keywordPlural, timeField, clickhouse),
- }
- }
- func InitOpsLog() {
- tmp := NewOpsLogManager(SOpsLog{}, "opslog_tbl", "event", "events", "ops_time", consts.OpsLogWithClickhouse)
- OpsLog = &tmp
- OpsLog.SetVirtualObject(OpsLog)
- opslogQueryWorkerMan = appsrv.NewWorkerManager("opslog_query_worker", 2, 512, true)
- opslogWriteWorkerMan = appsrv.NewWorkerManager("opslog_write_worker", 1, 2048, true)
- }
- func (manager *SOpsLogManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
- manager.SModelBaseManager.CustomizeHandlerInfo(info)
- switch info.GetName(nil) {
- case "list":
- info.SetProcessTimeout(time.Minute * 15).SetWorkerManager(opslogQueryWorkerMan)
- }
- }
- func (opslog *SOpsLog) GetName() string {
- return fmt.Sprintf("%s-%s", opslog.ObjType, opslog.Action)
- }
- func (opslog *SOpsLog) GetUpdatedAt() time.Time {
- return opslog.OpsTime
- }
- func (opslog *SOpsLog) GetUpdateVersion() int {
- return 1
- }
- func (opslog *SOpsLog) GetModelManager() IModelManager {
- return OpsLog
- }
- func (manager *SOpsLogManager) LogEvent(model IModel, action string, notes interface{}, userCred mcclient.TokenCredential) {
- if !consts.OpsLogEnabled() {
- return
- }
- var (
- objId = model.GetId()
- objName = model.GetName()
- )
- if objId == "" {
- if joint, ok := model.(IJointModel); ok {
- var (
- mm = JointMaster(joint)
- ms = JointSlave(joint)
- )
- if mm == nil || ms == nil {
- log.Errorf("logevent for jointmodel with nil sides %v/%v\n%s", mm, ms, debug.Stack())
- return
- }
- objId = mm.GetId() + "/" + ms.GetId()
- objName = mm.GetName() + "/" + ms.GetName()
- } else {
- log.Errorf("logevent for an object without ID: %T\n%s", model, debug.Stack())
- return
- }
- }
- if action == ACT_UPDATE {
- // skip empty diff
- if notes == nil {
- return
- }
- if uds, ok := notes.(sqlchemy.UpdateDiffs); ok && len(uds) == 0 {
- return
- }
- }
- opslog := &SOpsLog{
- OpsTime: time.Now().UTC(),
- ObjType: model.Keyword(),
- ObjId: objId,
- ObjName: objName,
- Action: action,
- Notes: stringutils.Interface2String(notes),
- }
- if userCred == nil {
- log.Warningf("Log event with empty userCred: objType=%s objId=%s objName=%s action=%s", model.Keyword(), objId, objName, action)
- const unknown = "unknown"
- opslog.ProjectId = unknown
- opslog.Project = unknown
- opslog.ProjectDomainId = unknown
- opslog.ProjectDomain = unknown
- opslog.UserId = unknown
- opslog.User = unknown
- opslog.DomainId = unknown
- opslog.Domain = unknown
- opslog.Roles = unknown
- } else {
- opslog.ProjectId = userCred.GetProjectId()
- opslog.Project = userCred.GetProjectName()
- opslog.ProjectDomainId = userCred.GetProjectDomainId()
- opslog.ProjectDomain = userCred.GetProjectDomain()
- opslog.UserId = userCred.GetUserId()
- opslog.User = userCred.GetUserName()
- opslog.DomainId = userCred.GetDomainId()
- opslog.Domain = userCred.GetDomainName()
- opslog.Roles = strings.Join(userCred.GetRoles(), ",")
- }
- opslog.SetModelManager(OpsLog, opslog)
- if virtualModel, ok := model.(IVirtualModel); ok {
- ownerId := virtualModel.GetOwnerId()
- if ownerId != nil {
- opslog.OwnerProjectId = ownerId.GetProjectId()
- opslog.OwnerDomainId = ownerId.GetProjectDomainId()
- }
- }
- opslogWriteWorkerMan.Run(opslog, nil, nil)
- }
- func (opslog *SOpsLog) Run() {
- err := OpsLog.TableSpec().Insert(ctx.CtxWithTime(), opslog)
- if err != nil {
- log.Errorf("fail to insert opslog: %s", err)
- }
- }
- func (opslog *SOpsLog) Dump() string {
- return fmt.Sprintf("[%s] %s %s", timeutils.CompactTime(opslog.OpsTime), opslog.Action, opslog.Notes)
- }
- func combineNotes(ctx context.Context, m2 IModel, notes jsonutils.JSONObject) *jsonutils.JSONDict {
- desc := m2.GetShortDesc(ctx)
- if notes != nil {
- if notesDict, ok := notes.(*jsonutils.JSONDict); ok {
- notesMap, _ := notesDict.GetMap()
- if notesMap != nil {
- for k, v := range notesMap {
- desc.Add(v, k)
- }
- }
- } else if notesArray, ok := notes.(*jsonutils.JSONArray); ok {
- noteList, _ := notesArray.GetArray()
- if noteList != nil {
- for i, v := range noteList {
- desc.Add(v, fmt.Sprintf("notes.%d", i))
- }
- }
- } else {
- desc.Add(jsonutils.NewString(notes.String()), "notes")
- }
- }
- return desc
- }
- func (manager *SOpsLogManager) logOneJointEvent(ctx context.Context, m1, m2 IModel, event string, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) {
- nn := notes
- if m2 != nil {
- nn = combineNotes(ctx, m2, notes)
- }
- manager.LogEvent(m1, event, nn, userCred)
- }
- func (manager *SOpsLogManager) logJoinEvent(ctx context.Context, m1, m2 IModel, event string, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) {
- if m1 != nil {
- manager.logOneJointEvent(ctx, m1, m2, event, userCred, notes)
- }
- if m2 != nil {
- manager.logOneJointEvent(ctx, m2, m1, event, userCred, notes)
- }
- }
- func (manager *SOpsLogManager) LogAttachEvent(ctx context.Context, m1, m2 IModel, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) {
- manager.logJoinEvent(ctx, m1, m2, ACT_ATTACH, userCred, notes)
- }
- func (manager *SOpsLogManager) LogDetachEvent(ctx context.Context, m1, m2 IModel, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) {
- manager.logJoinEvent(ctx, m1, m2, ACT_DETACH, userCred, notes)
- }
- // 操作日志列表
- func (manager *SOpsLogManager) ListItemFilter(
- ctx context.Context,
- q *sqlchemy.SQuery,
- userCred mcclient.TokenCredential,
- input apis.OpsLogListInput,
- ) (*sqlchemy.SQuery, error) {
- for idx, domainId := range input.OwnerDomainIds {
- domainObj, err := DefaultDomainFetcher(ctx, domainId)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2("domain", domainId)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- input.OwnerDomainIds[idx] = domainObj.GetId()
- }
- if len(input.OwnerDomainIds) > 0 {
- q = q.Filter(sqlchemy.In(q.Field("owner_domain_id"), input.OwnerDomainIds))
- }
- for idx, projectId := range input.OwnerProjectIds {
- domainId := ""
- if len(input.OwnerDomainIds) == 1 {
- domainId = input.OwnerDomainIds[0]
- }
- projObj, err := DefaultProjectFetcher(ctx, projectId, domainId)
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, httperrors.NewResourceNotFoundError2("project", projectId)
- } else {
- return nil, httperrors.NewGeneralError(err)
- }
- }
- input.OwnerProjectIds[idx] = projObj.GetId()
- }
- if len(input.OwnerProjectIds) > 0 {
- q = q.Filter(sqlchemy.In(q.Field("owner_tenant_id"), input.OwnerProjectIds))
- }
- if len(input.ObjTypes) > 0 {
- if len(input.ObjTypes) == 1 {
- q = q.Filter(sqlchemy.Equals(q.Field("obj_type"), input.ObjTypes[0]))
- } else {
- q = q.Filter(sqlchemy.In(q.Field("obj_type"), input.ObjTypes))
- }
- }
- if len(input.Objs) > 0 {
- if len(input.Objs) == 1 {
- q = q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("obj_id"), input.Objs[0]), sqlchemy.Equals(q.Field("obj_name"), input.Objs[0])))
- } else {
- q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("obj_id"), input.Objs), sqlchemy.In(q.Field("obj_name"), input.Objs)))
- }
- }
- if len(input.ObjIds) > 0 {
- if len(input.ObjIds) == 1 {
- q = q.Filter(sqlchemy.Equals(q.Field("obj_id"), input.ObjIds[0]))
- } else {
- q = q.Filter(sqlchemy.In(q.Field("obj_id"), input.ObjIds))
- }
- }
- if len(input.ObjNames) > 0 {
- if len(input.ObjNames) == 1 {
- q = q.Filter(sqlchemy.Equals(q.Field("obj_name"), input.ObjNames[0]))
- } else {
- q = q.Filter(sqlchemy.In(q.Field("obj_name"), input.ObjNames))
- }
- }
- if len(input.Actions) > 0 {
- if len(input.Actions) == 1 {
- q = q.Filter(sqlchemy.Equals(q.Field("action"), input.Actions[0]))
- } else {
- q = q.Filter(sqlchemy.In(q.Field("action"), input.Actions))
- }
- }
- //if !IsAdminAllowList(userCred, manager) {
- // q = q.Filter(sqlchemy.OR(
- // sqlchemy.Equals(q.Field("owner_tenant_id"), manager.GetOwnerId(userCred)),
- // sqlchemy.Equals(q.Field("tenant_id"), manager.GetOwnerId(userCred)),
- // ))
- //}
- if !input.Since.IsZero() {
- q = q.GT("ops_time", input.Since)
- }
- if !input.Until.IsZero() {
- q = q.LE("ops_time", input.Until)
- }
- return q, nil
- }
- func (manager *SOpsLogManager) SyncOwner(m IModel, former *STenant, userCred mcclient.TokenCredential) {
- notes := jsonutils.NewDict()
- notes.Add(jsonutils.NewString(former.GetProjectDomainId()), "former_domain_id")
- notes.Add(jsonutils.NewString(former.GetProjectDomain()), "former_domain")
- notes.Add(jsonutils.NewString(former.GetProjectId()), "former_project_id")
- notes.Add(jsonutils.NewString(former.GetProjectName()), "former_project")
- manager.LogEvent(m, ACT_CHANGE_OWNER, notes, userCred)
- }
- func (manager *SOpsLogManager) LogSyncUpdate(m IModel, uds sqlchemy.UpdateDiffs, userCred mcclient.TokenCredential) {
- if len(uds) > 0 {
- manager.LogEvent(m, ACT_SYNC_UPDATE, uds, userCred)
- }
- }
- func (self *SOpsLogManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man FilterByOwnerProvider, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
- if ownerId != nil {
- switch scope {
- case rbacscope.ScopeUser:
- if len(ownerId.GetUserId()) > 0 {
- /*
- * 默认只能查看本人发起的操作
- */
- q = q.Filter(sqlchemy.Equals(q.Field("user_id"), ownerId.GetUserId()))
- }
- case rbacscope.ScopeProject:
- if len(ownerId.GetProjectId()) > 0 {
- /*
- * 项目视图可以查看本项目人员发起的操作,或者对本项目资源实施的操作, QIU Jian
- */
- q = q.Filter(sqlchemy.OR(
- sqlchemy.Equals(q.Field("tenant_id"), ownerId.GetProjectId()),
- sqlchemy.Equals(q.Field("owner_tenant_id"), ownerId.GetProjectId()),
- ))
- }
- case rbacscope.ScopeDomain:
- if len(ownerId.GetProjectDomainId()) > 0 {
- /*
- * 域视图可以查看本域人员发起的操作,或者对本域资源实施的操作, QIU Jian
- */
- q = q.Filter(sqlchemy.OR(
- sqlchemy.Equals(q.Field("project_domain_id"), ownerId.GetProjectDomainId()),
- sqlchemy.Equals(q.Field("owner_domain_id"), ownerId.GetProjectDomainId()),
- ))
- }
- default:
- // systemScope, no filter
- }
- }
- return q
- }
- func (self *SOpsLog) GetOwnerId() mcclient.IIdentityProvider {
- owner := SOwnerId{
- Domain: self.ProjectDomain,
- DomainId: self.ProjectDomainId,
- Project: self.Project,
- ProjectId: self.ProjectId,
- User: self.User,
- UserId: self.UserId,
- UserDomain: self.Domain,
- UserDomainId: self.DomainId,
- }
- return &owner
- }
- func (self *SOpsLog) IsSharable(reqCred mcclient.IIdentityProvider) bool {
- return false
- }
- func (manager *SOpsLogManager) ResourceScope() rbacscope.TRbacScope {
- return rbacscope.ScopeUser
- }
- func (manager *SOpsLogManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
- ownerId := SOwnerId{}
- err := data.Unmarshal(&ownerId)
- if err != nil {
- return nil, errors.Wrap(err, "data.Unmarshal")
- }
- if ownerId.IsValid() {
- return &ownerId, nil
- }
- return FetchUserInfo(ctx, data)
- }
- func (manager *SOpsLogManager) ValidateCreateData(ctx context.Context,
- userCred mcclient.TokenCredential,
- ownerId mcclient.IIdentityProvider,
- query jsonutils.JSONObject,
- data apis.OpsLogCreateInput,
- ) (apis.OpsLogCreateInput, error) {
- data.User = ownerId.GetUserName()
- return data, nil
- }
- func (log *SOpsLog) CustomizeCreate(ctx context.Context,
- userCred mcclient.TokenCredential,
- ownerId mcclient.IIdentityProvider,
- query jsonutils.JSONObject,
- data jsonutils.JSONObject) error {
- log.User = ownerId.GetUserName()
- log.UserId = ownerId.GetUserId()
- log.Domain = ownerId.GetDomainName()
- log.DomainId = ownerId.GetDomainId()
- log.Project = ownerId.GetProjectName()
- log.ProjectId = ownerId.GetProjectId()
- log.ProjectDomain = ownerId.GetProjectDomain()
- log.ProjectDomainId = ownerId.GetProjectDomainId()
- return log.SModelBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
- }
- // override
- func (log *SOpsLog) GetRecordTime() time.Time {
- return log.OpsTime
- }
- func (manager *SOpsLogManager) FetchCustomizeColumns(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- objs []interface{},
- fields stringutils2.SSortedStrings,
- isList bool,
- ) []apis.OpsLogDetails {
- rows := make([]apis.OpsLogDetails, len(objs))
- projectIds := make([]string, len(rows))
- domainIds := make([]string, len(rows))
- for i := range rows {
- var base *SOpsLog
- err := reflectutils.FindAnonymouStructPointer(objs[i], &base)
- if err != nil {
- log.Errorf("Cannot find OpsLog in %#v: %s", objs[i], err)
- } else {
- if len(base.OwnerProjectId) > 0 {
- projectIds[i] = base.OwnerProjectId
- } else if len(base.OwnerDomainId) > 0 {
- domainIds[i] = base.OwnerDomainId
- }
- }
- }
- projects := DefaultProjectsFetcher(ctx, projectIds, false)
- domains := DefaultProjectsFetcher(ctx, domainIds, true)
- for i := range rows {
- if project, ok := projects[projectIds[i]]; ok {
- rows[i].OwnerProject = project.Name
- rows[i].OwnerDomain = project.Domain
- } else if domain, ok := domains[domainIds[i]]; ok {
- rows[i].OwnerDomain = domain.Name
- }
- }
- return rows
- }
|