github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/license-update.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "fmt" 23 "math/rand" 24 "time" 25 26 "github.com/minio/minio/internal/logger" 27 "github.com/tidwall/gjson" 28 ) 29 30 const ( 31 licUpdateCycle = 24 * time.Hour * 30 32 licRenewPath = "/api/cluster/renew-license" 33 ) 34 35 // initlicenseUpdateJob start the periodic license update job in the background. 36 func initLicenseUpdateJob(ctx context.Context, objAPI ObjectLayer) { 37 go func() { 38 r := rand.New(rand.NewSource(time.Now().UnixNano())) 39 // Leader node (that successfully acquires the lock inside licenceUpdaterLoop) 40 // will keep performing the license update. If the leader goes down for some 41 // reason, the lock will be released and another node will acquire it and 42 // take over because of this loop. 43 for { 44 licenceUpdaterLoop(ctx, objAPI) 45 46 // license update stopped for some reason. 47 // sleep for some time and try again. 48 duration := time.Duration(r.Float64() * float64(time.Hour)) 49 if duration < time.Second { 50 // Make sure to sleep at least a second to avoid high CPU ticks. 51 duration = time.Second 52 } 53 time.Sleep(duration) 54 } 55 }() 56 } 57 58 func licenceUpdaterLoop(ctx context.Context, objAPI ObjectLayer) { 59 ctx, cancel := globalLeaderLock.GetLock(ctx) 60 defer cancel() 61 62 licenseUpdateTimer := time.NewTimer(licUpdateCycle) 63 defer licenseUpdateTimer.Stop() 64 65 for { 66 select { 67 case <-ctx.Done(): 68 return 69 case <-licenseUpdateTimer.C: 70 71 if globalSubnetConfig.Registered() { 72 performLicenseUpdate(ctx, objAPI) 73 } 74 75 // Reset the timer for next cycle. 76 licenseUpdateTimer.Reset(licUpdateCycle) 77 } 78 } 79 } 80 81 func performLicenseUpdate(ctx context.Context, objectAPI ObjectLayer) { 82 // the subnet license renewal api renews the license only 83 // if required e.g. when it is expiring soon 84 url := globalSubnetConfig.BaseURL + licRenewPath 85 86 resp, err := globalSubnetConfig.Post(url, nil) 87 if err != nil { 88 logger.LogIf(ctx, fmt.Errorf("error from %s: %w", url, err)) 89 return 90 } 91 92 r := gjson.Parse(resp).Get("license_v2") 93 if r.Index == 0 { 94 logger.LogIf(ctx, fmt.Errorf("license not found in response from %s", url)) 95 return 96 } 97 98 lic := r.String() 99 if lic == globalSubnetConfig.License { 100 // license hasn't changed. 101 return 102 } 103 104 kv := "subnet license=" + lic 105 result, err := setConfigKV(ctx, objectAPI, []byte(kv)) 106 if err != nil { 107 logger.LogIf(ctx, fmt.Errorf("error setting subnet license config: %w", err)) 108 return 109 } 110 111 if result.Dynamic { 112 if err := applyDynamicConfigForSubSys(GlobalContext, objectAPI, result.Cfg, result.SubSys); err != nil { 113 logger.LogIf(ctx, fmt.Errorf("error applying subnet dynamic config: %w", err)) 114 return 115 } 116 globalNotificationSys.SignalConfigReload(result.SubSys) 117 } 118 }