From 40a922ac5863efb75df52cfc812fdb7fdffbffbf Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sat, 21 Dec 2019 01:16:16 +0800 Subject: [PATCH] feature: service config update support --- cmd/climc/shell/policies.go | 27 +---- cmd/climc/shell/services.go | 31 ++++++ pkg/apigateway/options/options.go | 11 ++ pkg/apigateway/service/service.go | 5 +- pkg/apis/identity/consts.go | 43 ++++++- pkg/appsrv/workers.go | 18 ++- pkg/appsrv/workers_watchdog.go | 78 +++++++++++++ pkg/cloudcommon/options/changes.go | 38 +++++++ pkg/cloudcommon/options/manager.go | 105 ++++++++++++++++++ pkg/cloudcommon/{app => options}/mergeconf.go | 70 +++++++----- pkg/cloudcommon/options/options.go | 4 +- pkg/compute/options/options.go | 10 ++ pkg/compute/service/service.go | 7 +- pkg/image/options/options.go | 11 ++ pkg/image/service/service.go | 5 +- pkg/keystone/models/configs.go | 104 ++++++++++------- pkg/keystone/models/identity_provider.go | 4 +- pkg/keystone/models/services.go | 15 ++- pkg/keystone/options/options.go | 13 ++- pkg/keystone/service/service.go | 9 +- pkg/util/shellutils/edit.go | 53 +++++++++ scripts/rbac/domainadmin.yaml | 4 +- scripts/rbac/member.yaml | 7 +- scripts/rbac/projectowner.yaml | 4 +- scripts/rbac/sysadmin.yaml | 2 +- 25 files changed, 549 insertions(+), 129 deletions(-) create mode 100644 pkg/appsrv/workers_watchdog.go create mode 100644 pkg/cloudcommon/options/changes.go create mode 100644 pkg/cloudcommon/options/manager.go rename pkg/cloudcommon/{app => options}/mergeconf.go (58%) create mode 100644 pkg/util/shellutils/edit.go diff --git a/cmd/climc/shell/policies.go b/cmd/climc/shell/policies.go index c72b2f496f..fd116c724f 100644 --- a/cmd/climc/shell/policies.go +++ b/cmd/climc/shell/policies.go @@ -17,8 +17,6 @@ package shell import ( "fmt" "io/ioutil" - "os" - "os/exec" "strings" "time" @@ -32,6 +30,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { @@ -212,33 +211,13 @@ func init() { return err } - tmpfile, err := ioutil.TempFile("", "policy-blob") - if err != nil { - return err - } - defer os.Remove(tmpfile.Name()) // clean up - - if _, err := tmpfile.Write([]byte(yaml)); err != nil { - return err - } - if err := tmpfile.Close(); err != nil { - return err - } - - cmd := exec.Command("vim", tmpfile.Name()) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - err = cmd.Run() + yaml, err = shellutils.Edit(yaml) if err != nil { return err } params := jsonutils.NewDict() - policyBytes, err := ioutil.ReadFile(tmpfile.Name()) - if err != nil { - return err - } - params.Add(jsonutils.NewString(string(policyBytes)), "policy") + params.Add(jsonutils.NewString(yaml), "policy") result, err = modules.Policies.Patch(s, policyId, params) if err != nil { diff --git a/cmd/climc/shell/services.go b/cmd/climc/shell/services.go index ee52d6c667..81bad2b179 100644 --- a/cmd/climc/shell/services.go +++ b/cmd/climc/shell/services.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { @@ -239,4 +240,34 @@ func init() { return nil }) + type ServiceConfigEditOptions struct { + SERVICE string `help:"service name or id"` + } + R(&ServiceConfigEditOptions{}, "service-config-edit", "Edit config yaml of a service", func(s *mcclient.ClientSession, args *ServiceConfigEditOptions) error { + conf, err := modules.ServicesV3.GetSpecific(s, args.SERVICE, "config", nil) + if err != nil { + return err + } + confJson, err := conf.Get("config") + if err != nil { + return err + } + content, err := shellutils.Edit(confJson.YAMLString()) + if err != nil { + return err + } + yamlJson, err := jsonutils.ParseYAML(content) + if err != nil { + return err + } + config := jsonutils.NewDict() + config.Add(yamlJson, "config") + nconf, err := modules.ServicesV3.PerformAction(s, args.SERVICE, "config", config) + if err != nil { + return err + } + fmt.Println(nconf.PrettyString()) + return nil + }) + } diff --git a/pkg/apigateway/options/options.go b/pkg/apigateway/options/options.go index 10015f3681..d8d3584f3e 100644 --- a/pkg/apigateway/options/options.go +++ b/pkg/apigateway/options/options.go @@ -35,3 +35,14 @@ type GatewayOptions struct { var ( Options GatewayOptions ) + +func OnOptionsChange(oldO, newO interface{}) bool { + oldOpts := oldO.(*GatewayOptions) + newOpts := newO.(*GatewayOptions) + + if common_options.OnCommonOptionsChange(&oldOpts.CommonOptions, &newOpts.CommonOptions) { + return true + } + + return false +} diff --git a/pkg/apigateway/service/service.go b/pkg/apigateway/service/service.go index e9e8727499..05201b1e61 100644 --- a/pkg/apigateway/service/service.go +++ b/pkg/apigateway/service/service.go @@ -41,10 +41,7 @@ func StartService() { log.Infof("Auth complete.") }) - err := app_common.MergeServiceConfig(opts, api.SERVICE_TYPE, api.SERVICE_VERSION) - if err != nil { - log.Fatalf("[MERGE CONFIG] Fail to merge service config %s", err) - } + common_options.StartOptionManager(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, api.SERVICE_VERSION, options.OnOptionsChange) if opts.DisableModuleApiVersion { mcclient.DisableApiVersionByModule() diff --git a/pkg/apis/identity/consts.go b/pkg/apis/identity/consts.go index 1b360c247c..3827240e53 100644 --- a/pkg/apis/identity/consts.go +++ b/pkg/apis/identity/consts.go @@ -89,17 +89,30 @@ var ( }, } - BlacklistOptionMap = map[string][]string{ + CommonWhitelistOptionMap = map[string][]string{ "default": []string{ - "region", - "sql_connection", + "default_quota_value", + "enable_rbac", + "non_default_domain_projects", + "time_zone", + }, + } + + ServiceBlacklistOptionMap = map[string][]string{ + "default": []string{ + "help", + "version", "config", + "pid_file", + + "region", "application_id", "log_level", + "log_verbose_level", "temp_path", - "auto_sync_table", "address", "port", + "port_v2", "admin_port", "notify_admin_users", "session_endpoint_type", @@ -107,12 +120,30 @@ var ( "admin_project", "admin_user", "auth_url", - "default_aws_instance_type_file", - "port_v2", "enable_ssl", "ssl_certfile", "ssl_keyfile", "ssl_ca_certs", + + "is_slave_node", + "config_sync_period_seconds", + + "sql_connection", + "auto_sync_table", + "exit_after_db_init", + "global_virtual_resource_namespace", + "debug_sqlchemy", + "lockman_method", + "etcd_lock_prefix", + "etcd_lock_ttl", + "etcd_endpoints", + "etcd_username", + "etcd_password", + "etcd_use_tls", + "etcd_skip_tls_verify", + "etcd_cacert", + "etcd_cert", + "etcd_key", }, } ) diff --git a/pkg/appsrv/workers.go b/pkg/appsrv/workers.go index 90c2732279..a6defdcb7d 100644 --- a/pkg/appsrv/workers.go +++ b/pkg/appsrv/workers.go @@ -37,10 +37,16 @@ func enableDebug() { isDebug = true } -var workerManagers []*SWorkerManager +var ( + workerManagers []*SWorkerManager + + workerManagerLock *sync.Mutex +) func init() { workerManagers = make([]*SWorkerManager, 0) + + workerManagerLock = &sync.Mutex{} } type SWorker struct { @@ -166,6 +172,9 @@ func NewWorkerManagerIgnoreOverflow(name string, workerCount int, backlog int, d ignoreOverflow: ignoreOverflow, } + workerManagerLock.Lock() + defer workerManagerLock.Unlock() + workerManagers = append(workerManagers, &manager) return &manager } @@ -252,6 +261,13 @@ type SWorkerManagerStates struct { DetachWorkerCnt int } +func (s SWorkerManagerStates) IsBusy() bool { + if s.QueueCnt == 0 && s.ActiveWorkerCnt == 0 && s.DetachWorkerCnt == 0 { + return false + } + return true +} + func (wm *SWorkerManager) getState() SWorkerManagerStates { state := SWorkerManagerStates{} diff --git a/pkg/appsrv/workers_watchdog.go b/pkg/appsrv/workers_watchdog.go new file mode 100644 index 0000000000..89df5d65cc --- /dev/null +++ b/pkg/appsrv/workers_watchdog.go @@ -0,0 +1,78 @@ +// 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 appsrv + +import ( + "time" + + "yunion.io/x/log" +) + +const ( + WATCHDOG_SLEEP_SECONDS = 30 +) + +var ( + busyWorkers map[*SWorkerManager]int + + exitFlag bool +) + +func init() { + busyWorkers = make(map[*SWorkerManager]int) + + watchdog() +} + +func watchdog() { + do_worker_watchdog() + + time.AfterFunc(time.Second*WATCHDOG_SLEEP_SECONDS, watchdog) +} + +func do_worker_watchdog() { + log.Debugf("worker manager watchdog runing") + + for _, w := range workerManagers { + stats := w.getState() + busy := stats.IsBusy() + if busy { + if _, ok := busyWorkers[w]; ok { + busyWorkers[w] += 1 + } else { + busyWorkers[w] = 1 + } + } else { + if _, ok := busyWorkers[w]; ok { + delete(busyWorkers, w) + } + } + } + if len(busyWorkers) > 0 { + for w, k := range busyWorkers { + if k > 1 { + log.Warningf("WorkerManager %s has been busy for %d cycles...", w.name, k) + } + } + } else { + if exitFlag { + log.Fatalln("System is idle, no worker is busy, exitFlag is set, to exit ...") + } + } +} + +func SetExitFlag() { + exitFlag = true +} diff --git a/pkg/cloudcommon/options/changes.go b/pkg/cloudcommon/options/changes.go new file mode 100644 index 0000000000..8fee9a1a8a --- /dev/null +++ b/pkg/cloudcommon/options/changes.go @@ -0,0 +1,38 @@ +// 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 options + +func OnBaseOptionsChange(oOpts, nOpts interface{}) bool { + oldOpts := oOpts.(*BaseOptions) + newOpts := nOpts.(*BaseOptions) + + if oldOpts.RequestWorkerCount != newOpts.RequestWorkerCount { + return true + } + if oldOpts.TimeZone != newOpts.TimeZone { + return true + } + return false +} + +func OnCommonOptionsChange(oOpts, nOpts interface{}) bool { + oldOpts := oOpts.(*CommonOptions) + newOpts := nOpts.(*CommonOptions) + + if OnBaseOptionsChange(&oldOpts.BaseOptions, &newOpts.BaseOptions) { + return true + } + return false +} diff --git a/pkg/cloudcommon/options/manager.go b/pkg/cloudcommon/options/manager.go new file mode 100644 index 0000000000..bc38d6f75f --- /dev/null +++ b/pkg/cloudcommon/options/manager.go @@ -0,0 +1,105 @@ +// 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 options + +import ( + "reflect" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" +) + +const ( + MIN_REFRESH_INTERVAL_SECONDS = 30 +) + +type TOptionsChangeFunc func(oldOpts, newOpts interface{}) bool + +type SOptionManager struct { + serviceType string + serviceVersion string + + options interface{} + + session IServiceConfigSession + + refreshInterval time.Duration + + onOptionsChange TOptionsChangeFunc +} + +var ( + OptionManager *SOptionManager +) + +func StartOptionManager(option interface{}, refreshSeconds int, serviceType, serviceVersion string, onChange TOptionsChangeFunc) { + StartOptionManagerWithSessionDriver(option, refreshSeconds, serviceType, serviceVersion, onChange, newServiceConfigSession()) +} + +func StartOptionManagerWithSessionDriver(options interface{}, refreshSeconds int, serviceType, serviceVersion string, onChange TOptionsChangeFunc, session IServiceConfigSession) { + log.Infof("OptionManager start to fetch service configs ...") + if refreshSeconds <= MIN_REFRESH_INTERVAL_SECONDS { + // a minimal 30 seconds refresh interval + refreshSeconds = MIN_REFRESH_INTERVAL_SECONDS + } + refreshInterval := time.Duration(refreshSeconds) * time.Second + OptionManager = &SOptionManager{ + serviceType: serviceType, + serviceVersion: serviceVersion, + options: options, + session: session, + refreshInterval: refreshInterval, + onOptionsChange: onChange, + } + OptionManager.firstSync() +} + +func (manager *SOptionManager) newOptions() interface{} { + optType := reflect.ValueOf(manager.options).Elem().Type() + return reflect.New(optType).Interface() +} + +func copyOptions(dst, src interface{}) { + dstValue := reflect.ValueOf(dst).Elem() + dstValue.Set(reflect.ValueOf(src).Elem()) +} + +func (manager *SOptionManager) doSync(first bool) { + newOpts := manager.newOptions() + copyOptions(newOpts, manager.options) + merged := manager.session.Merge(newOpts, manager.serviceType, manager.serviceVersion) + + if merged && !reflect.DeepEqual(newOpts, manager.options) { + log.Infof("Service config changed ...") + if !first && manager.onOptionsChange != nil && manager.onOptionsChange(manager.options, newOpts) { + log.Infof("Option changes detected and going to restart the program...") + appsrv.SetExitFlag() + } + copyOptions(manager.options, newOpts) + manager.session.Upload() + } +} + +func (manager *SOptionManager) firstSync() { + manager.doSync(true) + time.AfterFunc(manager.refreshInterval, manager.sync) +} + +func (manager *SOptionManager) sync() { + manager.doSync(false) + time.AfterFunc(manager.refreshInterval, manager.sync) +} diff --git a/pkg/cloudcommon/app/mergeconf.go b/pkg/cloudcommon/options/mergeconf.go similarity index 58% rename from pkg/cloudcommon/app/mergeconf.go rename to pkg/cloudcommon/options/mergeconf.go index 3c7b709d43..fc2965504c 100644 --- a/pkg/cloudcommon/app/mergeconf.go +++ b/pkg/cloudcommon/options/mergeconf.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package app +package options import ( "context" @@ -56,46 +56,66 @@ func getServiceConfig(s *mcclient.ClientSession, serviceId string) (jsonutils.JS return defConf, nil } -func MergeServiceConfig(opts interface{}, serviceType string, serviceVersion string) error { +type IServiceConfigSession interface { + Merge(opts interface{}, serviceType string, serviceVersion string) bool + Upload() +} + +type mcclientServiceConfigSession struct { + session *mcclient.ClientSession + serviceId string + config *jsonutils.JSONDict +} + +func newServiceConfigSession() IServiceConfigSession { + return &mcclientServiceConfigSession{} +} + +func (s *mcclientServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string) bool { merged := false - conf := jsonutils.Marshal(opts).(*jsonutils.JSONDict) - region, _ := conf.GetString("region") - epType, _ := conf.GetString("session_endpoint_type") - s := auth.AdminSession(context.Background(), region, "", epType, "") - serviceId, _ := getServiceIdByType(s, serviceType, serviceVersion) - if len(serviceId) > 0 { - serviceConf, err := getServiceConfig(s, serviceId) + s.config = jsonutils.Marshal(opts).(*jsonutils.JSONDict) + region, _ := s.config.GetString("region") + epType, _ := s.config.GetString("session_endpoint_type") + s.session = auth.AdminSession(context.Background(), region, "", epType, "") + s.serviceId, _ = getServiceIdByType(s.session, serviceType, serviceVersion) + if len(s.serviceId) > 0 { + serviceConf, err := getServiceConfig(s.session, s.serviceId) if err != nil { log.Errorf("getServiceConfig for %s failed: %s", serviceType, err) } else { - conf.Update(serviceConf) + s.config.Update(serviceConf) merged = true } } - commonServiceId, _ := getServiceIdByType(s, consts.COMMON_SERVICE, "") + commonServiceId, _ := getServiceIdByType(s.session, consts.COMMON_SERVICE, "") if len(commonServiceId) > 0 { - commonConf, err := getServiceConfig(s, commonServiceId) + commonConf, err := getServiceConfig(s.session, commonServiceId) if err != nil { log.Errorf("getServiceConfig for %s failed: %s", consts.COMMON_SERVICE, err) } else { - conf.Update(commonConf) + s.config.Update(commonConf) merged = true } } if merged { - err := conf.Unmarshal(opts) - if err != nil { - return errors.Wrap(err, "conf.Unmarshal") + err := s.config.Unmarshal(opts) + if err == nil { + return true } - if len(serviceId) > 0 { - nconf := jsonutils.NewDict() - nconf.Add(conf, "config", "default") - _, err := modules.ServicesV3.PerformAction(s, serviceId, "config", nconf) - if err != nil { - // ignore the error - log.Errorf("fail to save config: %s", err) - } + log.Errorf("s.config.Unmarshal fail %s", err) + } + return false +} + +func (s *mcclientServiceConfigSession) Upload() { + // upload service config + if len(s.serviceId) > 0 { + nconf := jsonutils.NewDict() + nconf.Add(s.config, "config", "default") + _, err := modules.ServicesV3.PerformAction(s.session, s.serviceId, "config", nconf) + if err != nil { + // ignore the error + log.Errorf("fail to save config: %s", err) } } - return nil } diff --git a/pkg/cloudcommon/options/options.go b/pkg/cloudcommon/options/options.go index 39d9f527a4..ea19123bf4 100644 --- a/pkg/cloudcommon/options/options.go +++ b/pkg/cloudcommon/options/options.go @@ -73,6 +73,8 @@ type BaseOptions struct { RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 5 minutes" default:"300"` RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"` + ConfigSyncPeriodSeconds int `help:"service config sync interval in seconds, default 300 seconds/5 minutes" default:"300"` + IsSlaveNode bool `help:"Region service slave node"` CronJobWorkerCount int `help:"Cron job worker count" default:"4"` @@ -80,7 +82,7 @@ type BaseOptions struct { CalculateQuotaUsageIntervalSeconds int `help:"interval to calculate quota usages, default 30 minutes" default:"900"` - NonDefaultDomainProjects bool `help:"allow projects in non-default domains" default:"false"` + NonDefaultDomainProjects bool `help:"allow projects in non-default domains" default:"false" json:",allowfalse"` TimeZone string `help:"time zone" default:"Asia/Shanghai"` diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 354107c0af..8f0f97ef88 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -133,3 +133,13 @@ type SCapabilityOptions struct { var ( Options ComputeOptions ) + +func OnOptionsChange(oldO, newO interface{}) bool { + oldOpts := oldO.(*ComputeOptions) + newOpts := newO.(*ComputeOptions) + + if common_options.OnCommonOptionsChange(&oldOpts.CommonOptions, &newOpts.CommonOptions) { + return true + } + return false +} diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index bf7c29fda7..ca9b2e84c2 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -61,14 +61,11 @@ func StartService() { db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB) defer cloudcommon.CloseDB() - err := app_common.MergeServiceConfig(opts, api.SERVICE_TYPE, api.SERVICE_VERSION) - if err != nil { - log.Fatalf("[MERGE CONFIG] Fail to merge service config %s", err) - } + common_options.StartOptionManager(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, api.SERVICE_VERSION, options.OnOptionsChange) options.InitNameSyncResources() - err = setInfluxdbRetentionPolicy() + err := setInfluxdbRetentionPolicy() if err != nil { log.Errorf("setInfluxdbRetentionPolicy fail: %s", err) } diff --git a/pkg/image/options/options.go b/pkg/image/options/options.go index 35602f1496..0e022a5b07 100644 --- a/pkg/image/options/options.go +++ b/pkg/image/options/options.go @@ -46,3 +46,14 @@ type SImageOptions struct { var ( Options SImageOptions ) + +func OnOptionsChange(oldO, newO interface{}) bool { + oldOpts := oldO.(*SImageOptions) + newOpts := newO.(*SImageOptions) + + if common_options.OnCommonOptionsChange(&oldOpts.CommonOptions, &newOpts.CommonOptions) { + return true + } + + return false +} diff --git a/pkg/image/service/service.go b/pkg/image/service/service.go index fdea2d54b8..40b252d4ff 100644 --- a/pkg/image/service/service.go +++ b/pkg/image/service/service.go @@ -93,10 +93,7 @@ func StartService() { db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB) - err := app_common.MergeServiceConfig(opts, api.SERVICE_TYPE, api.SERVICE_VERSION) - if err != nil { - log.Fatalf("[MERGE CONFIG] Fail to merge service config %s", err) - } + common_options.StartOptionManager(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, api.SERVICE_VERSION, options.OnOptionsChange) go models.CheckImages() diff --git a/pkg/keystone/models/configs.go b/pkg/keystone/models/configs.go index fe93f23582..29af6afe97 100644 --- a/pkg/keystone/models/configs.go +++ b/pkg/keystone/models/configs.go @@ -19,13 +19,14 @@ import ( "sort" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/keystone/options" + common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" ) type SConfigOptionManager struct { @@ -183,7 +184,7 @@ func (manager *SConfigOptionManager) syncConfigs(model db.IModel, newOpts TConfi return nil } -func getConfigOptions(conf api.TConfigs, model db.IModel, blackList map[string][]string, sensitiveList map[string][]string) (TConfigOptions, TConfigOptions) { +func getConfigOptions(conf api.TConfigs, model db.IModel, whiteList map[string][]string, blackList map[string][]string, sensitiveList map[string][]string) (TConfigOptions, TConfigOptions) { options := make(TConfigOptions, 0) sensitive := make(TConfigOptions, 0) for group, groupConf := range conf { @@ -194,12 +195,21 @@ func getConfigOptions(conf api.TConfigs, model db.IModel, blackList map[string][ opt.Group = group opt.Option = optKey opt.Value = optVal - if v, ok := blackList[group]; ok && utils.IsInStringArray(optKey, v) { - // skip - } else if v, ok := sensitiveList[group]; ok && utils.IsInStringArray(optKey, v) { + if v, ok := sensitiveList[group]; ok && utils.IsInStringArray(optKey, v) { sensitive = append(sensitive, opt) } else { - options = append(options, opt) + if whiteList != nil { + if v, ok := whiteList[group]; ok && utils.IsInStringArray(optKey, v) { + options = append(options, opt) + } + } else if blackList != nil { + if v, ok := blackList[group]; ok && utils.IsInStringArray(optKey, v) { + } else { + options = append(options, opt) + } + } else { + options = append(options, opt) + } } } } @@ -297,8 +307,8 @@ func GetConfigs(model db.IModel, all bool) (api.TConfigs, error) { return config2map(opts), nil } -func saveConfigs(action string, model db.IModel, opts api.TConfigs, blackList map[string][]string, sensitiveConfs map[string][]string) error { - whiteListedOpts, sensitiveOpts := getConfigOptions(opts, model, blackList, sensitiveConfs) +func saveConfigs(action string, model db.IModel, opts api.TConfigs, whiteList map[string][]string, blackList map[string][]string, sensitiveConfs map[string][]string) error { + whiteListedOpts, sensitiveOpts := getConfigOptions(opts, model, whiteList, blackList, sensitiveConfs) if action == "update" { err := WhitelistedConfigManager.updateConfigs(whiteListedOpts) if err != nil { @@ -330,47 +340,65 @@ func saveConfigs(action string, model db.IModel, opts api.TConfigs, blackList ma return nil } -func MergeServiceConfig(opts *options.SKeystoneOptions) error { +type dbServiceConfigSession struct { + config *jsonutils.JSONDict + service *SService +} + +func NewServiceConfigSession() common_options.IServiceConfigSession { + return &dbServiceConfigSession{} +} + +func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string) bool { merged := false - conf := jsonutils.Marshal(opts).(*jsonutils.JSONDict) - service, _ := ServiceManager.fetchServiceByType(api.SERVICE_TYPE) - if service != nil { - serviceConf, err := GetConfigs(service, false) + s.config = jsonutils.Marshal(opts).(*jsonutils.JSONDict) + s.service, _ = ServiceManager.fetchServiceByType(serviceType) + if s.service != nil { + serviceConf, err := GetConfigs(s.service, false) if err != nil { - return errors.Wrap(err, "GetConfigs service") + log.Errorf("GetConfigs for %s fail: %s", serviceType, err) + } else { + serviceConfJson := jsonutils.Marshal(serviceConf["default"]) + s.config.Update(serviceConfJson) + merged = true } - serviceConfJson := jsonutils.Marshal(serviceConf["default"]) - conf.Update(serviceConfJson) - merged = true } commonService, _ := ServiceManager.fetchServiceByType(consts.COMMON_SERVICE) if commonService != nil { commonConf, err := GetConfigs(commonService, false) if err != nil { - return errors.Wrap(err, "GetConfigs commonService") + log.Errorf("GetConfigs for %s fail: %s", consts.COMMON_SERVICE, err) + } else { + commonConfJson := jsonutils.Marshal(commonConf["default"]) + s.config.Update(commonConfJson) + merged = true } - commonConfJson := jsonutils.Marshal(commonConf["default"]) - conf.Update(commonConfJson) - merged = true } if merged { - err := conf.Unmarshal(opts) - if err != nil { - return errors.Wrap(err, "conf.Unmarshal") - } - if service != nil { - nconf := jsonutils.NewDict() - nconf.Add(conf, "default") - tconf := api.TConfigs{} - err = nconf.Unmarshal(tconf) - if err != nil { - return errors.Wrap(err, "conf.Unmarshal(tconf)") - } - err = saveConfigs("", service, tconf, api.BlacklistOptionMap, nil) - if err != nil { - return errors.Wrap(err, "saveConfigs") - } + err := s.config.Unmarshal(opts) + if err == nil { + return true } + log.Errorf("s.config.Unmarshal fail %s", err) + } + return false +} + +func (s *dbServiceConfigSession) Upload() { + if s.service == nil { + return + } + nconf := jsonutils.NewDict() + nconf.Add(s.config, "default") + tconf := api.TConfigs{} + err := nconf.Unmarshal(tconf) + if err != nil { + log.Errorf("nconf.Unmarshal fail %s", err) + return + } + err = saveConfigs("", s.service, tconf, nil, api.ServiceBlacklistOptionMap, nil) + if err != nil { + log.Errorf("saveConfigs fail %s", err) + return } - return nil } diff --git a/pkg/keystone/models/identity_provider.go b/pkg/keystone/models/identity_provider.go index 911a379ee6..3ccc949f4a 100644 --- a/pkg/keystone/models/identity_provider.go +++ b/pkg/keystone/models/identity_provider.go @@ -228,7 +228,7 @@ func (ident *SIdentityProvider) PerformConfig(ctx context.Context, userCred mccl return nil, httperrors.NewInputParameterError("invalid input data") } action, _ := data.GetString("action") - err = saveConfigs(action, ident, opts, nil, api.SensitiveDomainConfigMap) + err = saveConfigs(action, ident, opts, nil, nil, api.SensitiveDomainConfigMap) if err != nil { return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) } @@ -327,7 +327,7 @@ func (ident *SIdentityProvider) PostCreate(ctx context.Context, userCred mcclien log.Errorf("parse config error %s", err) return } - err = saveConfigs("", ident, opts, nil, api.SensitiveDomainConfigMap) + err = saveConfigs("", ident, opts, nil, nil, api.SensitiveDomainConfigMap) if err != nil { log.Errorf("saveConfig fail %s", err) return diff --git a/pkg/keystone/models/services.go b/pkg/keystone/models/services.go index 15ce42109e..126b7a9b7e 100644 --- a/pkg/keystone/models/services.go +++ b/pkg/keystone/models/services.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -159,6 +160,14 @@ func (service *SService) AllowPerformConfig(ctx context.Context, userCred mcclie return db.IsAdminAllowUpdateSpec(userCred, service, "config") } +func (service *SService) isCommonService() bool { + if service.Type == consts.COMMON_SERVICE { + return true + } else { + return false + } +} + func (service *SService) PerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (jsonutils.JSONObject, error) { action, _ := data.GetString("action") opts := api.TConfigs{} @@ -166,7 +175,11 @@ func (service *SService) PerformConfig(ctx context.Context, userCred mcclient.To if err != nil { return nil, httperrors.NewInputParameterError("invalid input data") } - err = saveConfigs(action, service, opts, api.BlacklistOptionMap, nil) + if service.isCommonService() { + err = saveConfigs(action, service, opts, api.CommonWhitelistOptionMap, nil, nil) + } else { + err = saveConfigs(action, service, opts, nil, api.ServiceBlacklistOptionMap, nil) + } if err != nil { return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) } diff --git a/pkg/keystone/options/options.go b/pkg/keystone/options/options.go index d4f5f97024..1acaf50629 100644 --- a/pkg/keystone/options/options.go +++ b/pkg/keystone/options/options.go @@ -27,7 +27,7 @@ type SKeystoneOptions struct { TokenExpirationSeconds int `default:"86400" help:"token expiration seconds" token:"expiration"` FernetKeyRepository string `help:"fernet key repo directory" token:"key_repository" default:"/etc/yunion/keystone/fernet-keys"` - SetupCredentialKeys bool `help:"setup standalone fernet keys for credentials" token:"setup_credential_key" default:"false"` + SetupCredentialKeys bool `help:"setup standalone fernet keys for credentials" token:"setup_credential_key" default:"false" json:",allowfalse"` BootstrapAdminUserPassword string `help:"bootstreap sysadmin user password" default:"sysadmin"` @@ -47,3 +47,14 @@ type SKeystoneOptions struct { var ( Options SKeystoneOptions ) + +func OnOptionsChange(oldOptions, newOptions interface{}) bool { + oldOpts := oldOptions.(*SKeystoneOptions) + newOpts := newOptions.(*SKeystoneOptions) + + if options.OnBaseOptionsChange(&oldOpts.BaseOptions, &newOpts.BaseOptions) { + return true + } + + return false +} diff --git a/pkg/keystone/service/service.go b/pkg/keystone/service/service.go index 612f0cf8e3..61ec7c89d0 100644 --- a/pkg/keystone/service/service.go +++ b/pkg/keystone/service/service.go @@ -21,15 +21,13 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/golang-plus/uuid" - "yunion.io/x/log" - api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon" app_common "yunion.io/x/onecloud/pkg/cloudcommon/app" "yunion.io/x/onecloud/pkg/cloudcommon/cronman" "yunion.io/x/onecloud/pkg/cloudcommon/db" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" // "yunion.io/x/onecloud/pkg/keystone/keys" + "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/keystone/cronjobs" _ "yunion.io/x/onecloud/pkg/keystone/driver/cas" _ "yunion.io/x/onecloud/pkg/keystone/driver/ldap" @@ -76,10 +74,7 @@ func StartService() { app_common.InitBaseAuth(&opts.BaseOptions) - err := models.MergeServiceConfig(opts) - if err != nil { - log.Fatalf("[MERGE CONFIG] Fail to merge service config: %s", err) - } + common_options.StartOptionManagerWithSessionDriver(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, "", options.OnOptionsChange, models.NewServiceConfigSession()) if !opts.IsSlaveNode { cron := cronman.InitCronJobManager(true, opts.CronJobWorkerCount) diff --git a/pkg/util/shellutils/edit.go b/pkg/util/shellutils/edit.go new file mode 100644 index 0000000000..896ccad73e --- /dev/null +++ b/pkg/util/shellutils/edit.go @@ -0,0 +1,53 @@ +// 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 shellutils + +import ( + "io/ioutil" + "os" + "os/exec" + + "yunion.io/x/pkg/errors" +) + +func Edit(yaml string) (string, error) { + tmpfile, err := ioutil.TempFile("", "policy-blob") + if err != nil { + return "", errors.Wrap(err, "ioutil.TempFile") + } + defer os.Remove(tmpfile.Name()) // clean up + + if _, err := tmpfile.Write([]byte(yaml)); err != nil { + return "", errors.Wrap(err, "tmpfile.Write") + } + if err := tmpfile.Close(); err != nil { + return "", errors.Wrap(err, "tmpfile.Close") + } + + cmd := exec.Command("vim", tmpfile.Name()) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + err = cmd.Run() + if err != nil { + return "", errors.Wrap(err, "cmd.Run") + } + + policyBytes, err := ioutil.ReadFile(tmpfile.Name()) + if err != nil { + return "", errors.Wrap(err, "ioutil.ReadFile") + } + + return string(policyBytes), nil +} diff --git a/scripts/rbac/domainadmin.yaml b/scripts/rbac/domainadmin.yaml index ed3cae32c7..7055cae468 100644 --- a/scripts/rbac/domainadmin.yaml +++ b/scripts/rbac/domainadmin.yaml @@ -3,6 +3,4 @@ roles: - domain_admin scope: domain policy: - *: - *: - *: allow + '*': allow diff --git a/scripts/rbac/member.yaml b/scripts/rbac/member.yaml index 7676ea6c4e..fe00061b88 100644 --- a/scripts/rbac/member.yaml +++ b/scripts/rbac/member.yaml @@ -1,8 +1,9 @@ # rbac for normal user, not allow for delete scope: project policy: - *: - *: - *: allow + '*': + '*': + '*': allow create: deny + update: deny delete: deny diff --git a/scripts/rbac/projectowner.yaml b/scripts/rbac/projectowner.yaml index 6ccf6ceb9a..c9c0990c8f 100644 --- a/scripts/rbac/projectowner.yaml +++ b/scripts/rbac/projectowner.yaml @@ -3,6 +3,4 @@ roles: - project_owner scope: project policy: - *: - *: - *: allow + '*': allow diff --git a/scripts/rbac/sysadmin.yaml b/scripts/rbac/sysadmin.yaml index 0bd67c27b7..8749adf33e 100644 --- a/scripts/rbac/sysadmin.yaml +++ b/scripts/rbac/sysadmin.yaml @@ -5,4 +5,4 @@ roles: - admin scope: system policy: - *: allow + '*': allow