github.com/google/cloudprober@v0.11.3/targets/lameduck/lameduck.go (about) 1 // Copyright 2017-2019 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package lameduck implements a lameducks provider. Lameduck provider fetches 16 // lameducks from the RTC (Runtime Configurator) service. This functionality 17 // allows an operator to do hitless VM upgrades. If a target is set to be in 18 // lameduck by the operator, it is taken out of the targets list. 19 package lameduck 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "net/http" 26 "sync" 27 28 "cloud.google.com/go/compute/metadata" 29 "github.com/golang/protobuf/proto" 30 "github.com/google/cloudprober/config/runconfig" 31 "github.com/google/cloudprober/logger" 32 rdsclient "github.com/google/cloudprober/rds/client" 33 rdsclientpb "github.com/google/cloudprober/rds/client/proto" 34 "github.com/google/cloudprober/rds/gcp" 35 rdspb "github.com/google/cloudprober/rds/proto" 36 "github.com/google/cloudprober/rds/server" 37 serverconfigpb "github.com/google/cloudprober/rds/server/proto" 38 "github.com/google/cloudprober/targets/endpoint" 39 configpb "github.com/google/cloudprober/targets/lameduck/proto" 40 targetspb "github.com/google/cloudprober/targets/proto" 41 "github.com/google/cloudprober/targets/rtc/rtcservice" 42 ) 43 44 // Lameducker provides an interface to Lameduck/Unlameduck an instance. 45 // 46 // Cloudprober doesn't currently (as of July, 2018) use this interface by 47 // itself. It's provided here so that other software (e.g. probing deployment 48 // management software) can lameduck/unlameduck instances in a way that 49 // Cloudprober understands. 50 type Lameducker interface { 51 Lameduck(name string) error 52 Unlameduck(name string) error 53 } 54 55 // global.lister is a singleton Lister. It caches data from the upstream config 56 // service, allowing for multiple consumers to lookup for lameducks without 57 // increasing load on the upstream service. 58 var global struct { 59 mu sync.RWMutex 60 lister endpoint.Lister 61 } 62 63 // service provides methods to do lameduck operations on VMs. 64 type service struct { 65 rtc rtcservice.Config 66 opts *configpb.Options 67 l *logger.Logger 68 } 69 70 // Lameduck puts the target in lameduck mode. 71 func (ldSvc *service) Lameduck(name string) error { 72 return ldSvc.rtc.Write(name, []byte{0}) 73 } 74 75 // Unlameduck removes the target from lameduck mode. 76 func (ldSvc *service) Unlameduck(name string) error { 77 err := ldSvc.rtc.Delete(name) 78 return err 79 } 80 81 // NewService creates a new lameduck service using the provided config options 82 // and an oauth2 enabled *http.Client; if the client is set to nil, an oauth 83 // enabled client is created automatically using GCP default credentials. 84 func newService(opts *configpb.Options, proj string, hc *http.Client, l *logger.Logger) (*service, error) { 85 if opts == nil { 86 return nil, fmt.Errorf("lameduck.Init: failed to construct lameduck Service: no lameDuckOptions given") 87 } 88 if l == nil { 89 l = &logger.Logger{} 90 } 91 92 cfg := opts.GetRuntimeconfigName() 93 94 rtc, err := rtcservice.New(proj, cfg, hc) 95 if err != nil { 96 return nil, fmt.Errorf("lameduck.Init : rtcconfig service initialization failed : %v", err) 97 } 98 99 return &service{ 100 rtc: rtc, 101 opts: opts, 102 l: l, 103 }, nil 104 } 105 106 func getProject(opts *configpb.Options) (string, error) { 107 project := opts.GetRuntimeconfigProject() 108 if project == "" { 109 var err error 110 project, err = metadata.ProjectID() 111 if err != nil { 112 return "", fmt.Errorf("lameduck.getProject: error while getting project id: %v", err) 113 } 114 } 115 return project, nil 116 } 117 118 // NewLameducker creates a new lameducker using the provided config and an 119 // oauth2 enabled *http.Client; if the client is set to nil, an oauth enabled 120 // client is created automatically using GCP default credentials. 121 func NewLameducker(opts *configpb.Options, hc *http.Client, l *logger.Logger) (Lameducker, error) { 122 project, err := getProject(opts) 123 if err != nil { 124 return nil, err 125 } 126 return newService(opts, project, hc, l) 127 } 128 129 func (li *lister) newRDSServer() (*server.Server, error) { 130 resTypes := make(map[string]string) 131 if li.rtcConfig != "" { 132 resTypes[gcp.ResourceTypes.RTCVariables] = li.rtcConfig 133 } 134 if li.pubsubTopic != "" { 135 resTypes[gcp.ResourceTypes.PubsubMessages] = li.pubsubTopic 136 } 137 138 pc := gcp.DefaultProviderConfig([]string{li.project}, resTypes, int(li.opts.GetReEvalSec()), "") 139 return server.New(context.Background(), &serverconfigpb.ServerConf{Provider: []*serverconfigpb.Provider{pc}}, nil, li.l) 140 } 141 142 func (li *lister) rdsClient(baseResourcePath string, additionalFilter *rdspb.Filter) (*rdsclient.Client, error) { 143 rdsClientConf := &rdsclientpb.ClientConf{ 144 ServerOptions: li.rdsServerOpts, 145 Request: &rdspb.ListResourcesRequest{ 146 Provider: proto.String("gcp"), 147 ResourcePath: proto.String(fmt.Sprintf("%s/%s", baseResourcePath, li.project)), 148 Filter: []*rdspb.Filter{ 149 { 150 Key: proto.String("updated_within"), 151 Value: proto.String(fmt.Sprintf("%ds", li.opts.GetExpirationSec())), 152 }, 153 }, 154 }, 155 ReEvalSec: proto.Int32(li.opts.GetReEvalSec()), 156 } 157 158 if additionalFilter != nil { 159 rdsClientConf.Request.Filter = append(rdsClientConf.Request.Filter, additionalFilter) 160 } 161 162 return rdsclient.New(rdsClientConf, li.listResourcesFunc, li.l) 163 } 164 165 func (li *lister) initClients() error { 166 if li.rtcConfig != "" { 167 li.l.Infof("lameduck: creating RDS client for RTC variables") 168 169 additionalFilter := &rdspb.Filter{ 170 Key: proto.String("config_name"), 171 Value: proto.String(li.opts.GetRuntimeconfigName()), 172 } 173 174 cl, err := li.rdsClient("rtc_variables", additionalFilter) 175 if err != nil { 176 return err 177 } 178 li.clients = append(li.clients, cl) 179 } 180 181 if li.pubsubTopic != "" { 182 li.l.Infof("lameduck: creating RDS client for PubSub messages") 183 184 // Here we assume that subscription name contains the topic name. This is 185 // true for the RDS implmentation. 186 additionalFilter := &rdspb.Filter{ 187 Key: proto.String("subscription"), 188 Value: proto.String(li.pubsubTopic), 189 } 190 191 cl, err := li.rdsClient("pubsub_messages", additionalFilter) 192 if err != nil { 193 return err 194 } 195 li.clients = append(li.clients, cl) 196 } 197 198 return nil 199 } 200 201 func (li *lister) ListEndpoints() []endpoint.Endpoint { 202 var result []endpoint.Endpoint 203 for _, cl := range li.clients { 204 result = append(result, cl.ListEndpoints()...) 205 } 206 207 if len(result) != 0 { 208 li.l.Infof("Lameducked targets: %v", result) 209 } 210 return result 211 } 212 213 type lister struct { 214 opts *configpb.Options 215 project string 216 rtcConfig string 217 pubsubTopic string 218 rdsServerOpts *rdsclientpb.ClientConf_ServerOptions 219 listResourcesFunc rdsclient.ListResourcesFunc 220 clients []*rdsclient.Client 221 l *logger.Logger 222 } 223 224 func newLister(globalOpts *targetspb.GlobalTargetsOptions, l *logger.Logger) (*lister, error) { 225 opts := globalOpts.GetLameDuckOptions() 226 li := &lister{ 227 opts: opts, 228 rdsServerOpts: globalOpts.GetRdsServerOptions(), 229 rtcConfig: opts.GetRuntimeconfigName(), 230 pubsubTopic: opts.GetPubsubTopic(), 231 l: l, 232 } 233 234 var err error 235 li.project, err = getProject(opts) 236 if err != nil { 237 return nil, err 238 } 239 240 // If there are lameduck specific RDS server options, use them. 241 if li.opts.GetRdsServerOptions() != nil { 242 li.rdsServerOpts = li.opts.GetRdsServerOptions() 243 } 244 245 // If no RDS server options are configured, look for a local one. 246 if li.rdsServerOpts == nil { 247 localRDSServer := runconfig.LocalRDSServer() 248 if localRDSServer == nil { 249 li.l.Infof("rds_server_address not given and found no local RDS server, creating a new one.") 250 251 var err error 252 localRDSServer, err = li.newRDSServer() 253 if err != nil { 254 return nil, fmt.Errorf("error while creating local RDS server: %v", err) 255 } 256 } 257 li.listResourcesFunc = localRDSServer.ListResources 258 } 259 260 return li, li.initClients() 261 } 262 263 // InitDefaultLister initializes the package using the given arguments. If a 264 // lister is given in the arguments, global.lister is set to that, otherwise a 265 // new lameduck service is created using the config options, and global.lister 266 // is set to that service. Initiating the package from a given lister is useful 267 // for testing pacakges that depend on this package. 268 func InitDefaultLister(globalOpts *targetspb.GlobalTargetsOptions, lister endpoint.Lister, l *logger.Logger) error { 269 global.mu.Lock() 270 defer global.mu.Unlock() 271 272 // Make sure we initialize global.lister only once. 273 if global.lister != nil { 274 return nil 275 } 276 277 // If a lister has been provided, use that. It's useful for testing. 278 if lister != nil { 279 global.lister = lister 280 return nil 281 } 282 283 if globalOpts.GetLameDuckOptions().GetUseRds() { 284 l.Warning("lameduck: use_rds doesn't do anything anymore and will soon be removed.") 285 } 286 287 lister, err := newLister(globalOpts, l) 288 if err != nil { 289 return err 290 } 291 292 global.lister = lister 293 return nil 294 } 295 296 // GetDefaultLister returns the global Lister. If global lister is 297 // uninitialized, it returns an error. 298 func GetDefaultLister() (endpoint.Lister, error) { 299 global.mu.RLock() 300 defer global.mu.RUnlock() 301 if global.lister == nil { 302 return nil, errors.New("global lameduck service not initialized") 303 } 304 return global.lister, nil 305 }