diff --git a/cmd/climc/shell/yunionconf/bugreport.go b/cmd/climc/shell/yunionconf/bugreport.go new file mode 100644 index 00000000000..45ca79487f5 --- /dev/null +++ b/cmd/climc/shell/yunionconf/bugreport.go @@ -0,0 +1,47 @@ +// 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 yunionconf + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" +) + +func init() { + type BugReportStatusOptions struct { + } + + R(&BugReportStatusOptions{}, "bug-report-status", "Show bug report status", func(s *mcclient.ClientSession, args *BugReportStatusOptions) error { + ret, err := yunionconf.BugReport.GetBugReportEnabled(s, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + + type BugReportEnableOptions struct { + } + + R(&BugReportEnableOptions{}, "bug-report-enable", "Enable bug report", func(s *mcclient.ClientSession, args *BugReportEnableOptions) error { + ret, err := yunionconf.BugReport.DoBugReportEnable(s, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + +} diff --git a/pkg/baremetal/tasks/worker.go b/pkg/baremetal/tasks/worker.go index 4a2606beccf..6d9ba06d949 100644 --- a/pkg/baremetal/tasks/worker.go +++ b/pkg/baremetal/tasks/worker.go @@ -22,10 +22,13 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/baremetal/options" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) var baremetalTaskWorkerMan *appsrv.SWorkerManager @@ -78,6 +81,7 @@ func executeTask(task ITask, args interface{}) { log.Errorf("Execute task panic: %v", err) debug.PrintStack() SetTaskFail(task, fmt.Errorf("%v", err)) + yunionconf.BugReport.SendBugReport(context.Background(), version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", err)) } }() err := curStage(context.Background(), args) diff --git a/pkg/cloudcommon/cronman/cronman.go b/pkg/cloudcommon/cronman/cronman.go index 065284e7aa9..e862875f6ed 100644 --- a/pkg/cloudcommon/cronman/cronman.go +++ b/pkg/cloudcommon/cronman/cronman.go @@ -25,12 +25,14 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/appctx" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/elect" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) var ( @@ -444,6 +446,7 @@ func (job *SCronJob) runJobInWorker(isStart bool, startTime time.Time) { if r := recover(); r != nil { log.Errorf("CronJob task %s run error: %s", job.Name, r) debug.PrintStack() + yunionconf.BugReport.SendBugReport(context.Background(), version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", r)) } }() diff --git a/pkg/cloudcommon/db/taskman/localtaskworker.go b/pkg/cloudcommon/db/taskman/localtaskworker.go index 75e1365ba5a..200a0b09a8e 100644 --- a/pkg/cloudcommon/db/taskman/localtaskworker.go +++ b/pkg/cloudcommon/db/taskman/localtaskworker.go @@ -15,13 +15,17 @@ package taskman import ( + "context" "fmt" "runtime/debug" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) var localTaskWorkerMan *appsrv.SWorkerManager @@ -48,6 +52,7 @@ func (t *localTask) Run() { defer func() { if r := recover(); r != nil { + yunionconf.BugReport.SendBugReport(context.Background(), version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", r)) log.Errorf("LocalTaskRun error: %s", r) debug.PrintStack() t.task.ScheduleRun(Error2TaskData(fmt.Errorf("LocalTaskRun error: %s", r))) diff --git a/pkg/cloudcommon/db/taskman/tasks.go b/pkg/cloudcommon/db/taskman/tasks.go index d3ae9ea5477..664a8418fbc 100644 --- a/pkg/cloudcommon/db/taskman/tasks.go +++ b/pkg/cloudcommon/db/taskman/tasks.go @@ -47,6 +47,7 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -544,6 +545,7 @@ func execITask(taskValue reflect.Value, task *STask, odata jsonutils.JSONObject, if r := recover(); r != nil { // call set stage failed, should not call task.SetStageFailed // func SetStageFailed may be overloading + yunionconf.BugReport.SendBugReport(ctx, version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", r)) log.Errorf("Task %s PANIC on stage %s: %v \n%s", task.TaskName, stageName, r, debug.Stack()) SetStageFailedFuncValue := taskValue.MethodByName("SetStageFailed") SetStageFailedFuncValue.Call( diff --git a/pkg/cloudcommon/workmanager/manager.go b/pkg/cloudcommon/workmanager/manager.go index 2d0668a0604..075c283e736 100644 --- a/pkg/cloudcommon/workmanager/manager.go +++ b/pkg/cloudcommon/workmanager/manager.go @@ -23,8 +23,11 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/appctx" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) type DelayTaskFunc func(context.Context, interface{}) (jsonutils.JSONObject, error) @@ -69,6 +72,7 @@ func (t *workerTask) Run() { defer func() { if r := recover(); r != nil { log.Errorf("DelayTask panic: %s", r) + yunionconf.BugReport.SendBugReport(t.ctx, version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", r)) debug.PrintStack() switch val := r.(type) { case string: @@ -139,6 +143,7 @@ func (t *delayWorkerTask) Run() { if r := recover(); r != nil { log.Errorln("DelayTaskWithoutReqctx panic: ", r) debug.PrintStack() + yunionconf.BugReport.SendBugReport(t.ctx, version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", r)) } }() diff --git a/pkg/cloudproxy/agent/worker/worker.go b/pkg/cloudproxy/agent/worker/worker.go index 37694596afc..e5670254a05 100644 --- a/pkg/cloudproxy/agent/worker/worker.go +++ b/pkg/cloudproxy/agent/worker/worker.go @@ -25,6 +25,7 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/apihelper" @@ -35,6 +36,7 @@ import ( agentssh "yunion.io/x/onecloud/pkg/cloudproxy/agent/ssh" "yunion.io/x/onecloud/pkg/mcclient/auth" cloudproxy_modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" "yunion.io/x/onecloud/pkg/util/netutils2" ssh_util "yunion.io/x/onecloud/pkg/util/ssh" ) @@ -207,6 +209,7 @@ func (w *Worker) Start(ctx context.Context) { func (w *Worker) run(ctx context.Context, mss *agentmodels.ModelSets) (err error) { defer func() { if panicVal := recover(); panicVal != nil { + yunionconf.BugReport.SendBugReport(context.Background(), version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", panicVal)) if panicErr, ok := panicVal.(runtime.Error); ok { err = errors.Wrap(panicErr, string(debug.Stack())) } else if panicErr, ok := panicVal.(error); ok { diff --git a/pkg/compute/models/syncworkers.go b/pkg/compute/models/syncworkers.go index a2c04a4b69f..74ce93913c8 100644 --- a/pkg/compute/models/syncworkers.go +++ b/pkg/compute/models/syncworkers.go @@ -24,10 +24,12 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/version" api "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) var ( @@ -85,6 +87,7 @@ func RunSyncCloudproviderRegionTask(ctx context.Context, key string, syncFunc fu data.Add(jsonutils.NewString(string(debug.Stack())), "stack") data.Add(jsonutils.NewString(err.Error()), "error") notifyclient.SystemExceptionNotify(context.TODO(), api.ActionSystemPanic, api.TOPIC_RESOURCE_TASK, data) + yunionconf.BugReport.SendBugReport(ctx, version.GetShortString(), string(debug.Stack()), err) }) } @@ -100,5 +103,6 @@ func RunSyncCloudAccountTask(ctx context.Context, probeFunc func()) { data.Add(jsonutils.NewString(string(debug.Stack())), "stack") data.Add(jsonutils.NewString(err.Error()), "error") notifyclient.SystemExceptionNotify(context.TODO(), api.ActionSystemPanic, api.TOPIC_RESOURCE_TASK, data) + yunionconf.BugReport.SendBugReport(ctx, version.GetShortString(), string(debug.Stack()), err) }) } diff --git a/pkg/mcclient/modules/yunionconf/mod_bugreport.go b/pkg/mcclient/modules/yunionconf/mod_bugreport.go new file mode 100644 index 00000000000..ce2364a2f14 --- /dev/null +++ b/pkg/mcclient/modules/yunionconf/mod_bugreport.go @@ -0,0 +1,60 @@ +// 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 yunionconf + +import ( + "context" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +type BugReportManager struct { + modulebase.ResourceManager +} + +var ( + BugReport BugReportManager +) + +func init() { + BugReport = BugReportManager{modules.NewYunionConfManager("bug-report", "bug-report", + []string{}, + []string{}, + )} + modules.Register(&BugReport) +} + +func (m BugReportManager) DoBugReportEnable(s *mcclient.ClientSession, _ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return modulebase.Post(m.ResourceManager, s, "enable-bug-report", nil, "") +} + +func (m BugReportManager) GetBugReportEnabled(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return modulebase.Get(m.ResourceManager, s, "bug-report-status", "") +} + +func (m BugReportManager) SendBugReport(ctx context.Context, version, stack string, err error) (jsonutils.JSONObject, error) { + msg := map[string]interface{}{ + "version": version, + "stack": stack, + "message": err.Error(), + } + s := auth.GetAdminSession(ctx, "") + return modulebase.Post(m.ResourceManager, s, "send-bug-report", jsonutils.Marshal(msg), "") +} diff --git a/pkg/util/atexit/atexit.go b/pkg/util/atexit/atexit.go index 43ef6fbbba2..a75063a3d5b 100644 --- a/pkg/util/atexit/atexit.go +++ b/pkg/util/atexit/atexit.go @@ -15,10 +15,16 @@ package atexit import ( + "context" "os" "runtime/debug" "sort" "sync" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/version" + + "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) // ExitHandlerFunc is the type of handler func @@ -95,6 +101,7 @@ func Handle() { if val != nil { print("panic ", val, "\n") debug.PrintStack() + yunionconf.BugReport.SendBugReport(context.Background(), version.GetShortString(), string(debug.Stack()), errors.Errorf("%s", val)) } }() eh.Func(eh) diff --git a/pkg/yunionconf/models/parameters.go b/pkg/yunionconf/models/parameters.go index c0d4f91e8d0..41ca2c50dac 100644 --- a/pkg/yunionconf/models/parameters.go +++ b/pkg/yunionconf/models/parameters.go @@ -37,8 +37,9 @@ import ( ) const ( - NAMESPACE_USER = "user" - NAMESPACE_SERVICE = "service" + NAMESPACE_USER = "user" + NAMESPACE_SERVICE = "service" + NAMESPACE_BUG_REPORT = "bug-report" ) type SParameterManager struct { @@ -364,3 +365,35 @@ func (model *SParameter) GetId() string { func (model *SParameter) GetName() string { return model.Name } + +var bugReportEnable *bool = nil + +func (manager *SParameterManager) GetBugReportEnabled() bool { + if bugReportEnable != nil { + return *bugReportEnable + } + enabled := manager.Query().Equals("namespace", NAMESPACE_BUG_REPORT).Count() > 0 + bugReportEnable = &enabled + return enabled +} + +func (manager *SParameterManager) EnableBugReport(ctx context.Context) bool { + if manager.GetBugReportEnabled() { + return true + } + res := &SParameter{ + Namespace: NAMESPACE_BUG_REPORT, + NamespaceId: NAMESPACE_BUG_REPORT, + Name: NAMESPACE_BUG_REPORT, + Value: jsonutils.NewDict(), + CreatedBy: api.SERVICE_TYPE, + } + res.SetModelManager(manager, res) + err := manager.TableSpec().Insert(ctx, res) + if err != nil { + return false + } + enabled := true + bugReportEnable = &enabled + return true +} diff --git a/pkg/yunionconf/service/handlers.go b/pkg/yunionconf/service/handlers.go index a692e74d74b..9cc234f1383 100644 --- a/pkg/yunionconf/service/handlers.go +++ b/pkg/yunionconf/service/handlers.go @@ -15,9 +15,20 @@ package service import ( + "context" + "encoding/base64" + "fmt" + "net/http" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/appsrv/dispatcher" + "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules/identity" "yunion.io/x/onecloud/pkg/yunionconf/models" ) @@ -25,6 +36,7 @@ func InitHandlers(app *appsrv.Application) { db.InitAllManagers() db.AddScopeResourceCountHandler("", app) + addBugReportHandler("", app) for _, manager := range []db.IModelManager{ db.UserCacheManager, @@ -50,3 +62,48 @@ func InitHandlers(app *appsrv.Application) { } } } + +func addBugReportHandler(prefix string, app *appsrv.Application) { + app.AddHandler("GET", fmt.Sprintf("%s/bug-report-status", prefix), bugReportStatusHandler) + app.AddHandler("POST", fmt.Sprintf("%s/enable-bug-report", prefix), enableBugReportHandler) + app.AddHandler("POST", fmt.Sprintf("%s/send-bug-report", prefix), sendBugReportHandler) +} + +func bugReportStatusHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + enabled := models.ParameterManager.GetBugReportEnabled() + appsrv.SendJSON(w, jsonutils.Marshal(map[string]bool{"enabled": enabled})) +} + +func enableBugReportHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + enabled := models.ParameterManager.EnableBugReport(ctx) + appsrv.SendJSON(w, jsonutils.Marshal(map[string]bool{"enabled": enabled})) +} + +func sendBugReportHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if !models.ParameterManager.GetBugReportEnabled() { + appsrv.SendJSON(w, jsonutils.Marshal(map[string]bool{"status": false})) + return + } + _, _, body := appsrv.FetchEnv(ctx, w, r) + apiServer := options.Options.ApiServer + if len(apiServer) == 0 { + s := auth.GetAdminSession(ctx, options.Options.Region) + commonCfg, _ := identity.ServicesV3.GetSpecific(s, "common", "config", nil) + if commonCfg != nil { + _apiServer, _ := commonCfg.GetString("config", "default", "api_server") + if len(_apiServer) > 0 { + apiServer = _apiServer + } + } + } + params := jsonutils.NewDict() + params.Set("api_server", jsonutils.NewString(apiServer)) + params.Update(body) + url, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9jbG91ZC55dW5pb24uY24vYXBpL3YyL2J1Zy1yZXBvcnQ=") + _, _, err := httputils.JSONRequest(nil, ctx, httputils.POST, string(url), nil, params, false) + if err != nil { + appsrv.SendJSON(w, jsonutils.Marshal(map[string]bool{"status": false})) + return + } + appsrv.SendJSON(w, jsonutils.Marshal(map[string]bool{"status": true})) +}