github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/metricsmanager/metricsmanager.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package metricsmanager contains the implementation of an api endpoint 5 // for calling metrics functions in state. 6 package metricsmanager 7 8 import ( 9 "fmt" 10 "strings" 11 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/os" 16 "github.com/juju/os/series" 17 "github.com/juju/utils" 18 "gopkg.in/juju/names.v2" 19 20 "github.com/juju/juju/apiserver/common" 21 "github.com/juju/juju/apiserver/facade" 22 "github.com/juju/juju/apiserver/facades/agent/metricsender" 23 "github.com/juju/juju/apiserver/params" 24 "github.com/juju/juju/core/instance" 25 "github.com/juju/juju/state" 26 ) 27 28 var ( 29 logger = loggo.GetLogger("juju.apiserver.metricsmanager") 30 maxBatchesPerSend = metricsender.DefaultMaxBatchesPerSend() 31 senderFactory = metricsender.DefaultSenderFactory() 32 ) 33 34 // MetricsManager defines the methods on the metricsmanager API end point. 35 type MetricsManager interface { 36 CleanupOldMetrics(arg params.Entities) (params.ErrorResults, error) 37 SendMetrics(args params.Entities) (params.ErrorResults, error) 38 } 39 40 // MetricsManagerAPI implements the metrics manager interface and is the concrete 41 // implementation of the api end point. 42 type MetricsManagerAPI struct { 43 state *state.State 44 pool *state.StatePool 45 model *state.Model 46 accessModel common.GetAuthFunc 47 clock clock.Clock 48 sender metricsender.MetricSender 49 } 50 51 var _ MetricsManager = (*MetricsManagerAPI)(nil) 52 53 // NewFacade wraps NewMetricsManagerAPI for API registration. 54 func NewFacade(ctx facade.Context) (*MetricsManagerAPI, error) { 55 return NewMetricsManagerAPI( 56 ctx.State(), 57 ctx.Resources(), 58 ctx.Auth(), 59 ctx.StatePool(), 60 clock.WallClock, 61 ) 62 } 63 64 type modelBackend struct { 65 *state.State 66 *state.Model 67 } 68 69 // NewMetricsManagerAPI creates a new API endpoint for calling metrics manager functions. 70 func NewMetricsManagerAPI( 71 st *state.State, 72 resources facade.Resources, 73 authorizer facade.Authorizer, 74 pool *state.StatePool, 75 clock clock.Clock, 76 ) (*MetricsManagerAPI, error) { 77 if !authorizer.AuthController() { 78 return nil, common.ErrPerm 79 } 80 81 m, err := st.Model() 82 if err != nil { 83 return nil, errors.Trace(err) 84 } 85 86 // Allow access only to the current model. 87 accessModel := func() (common.AuthFunc, error) { 88 return func(tag names.Tag) bool { 89 if tag == nil { 90 return false 91 } 92 return tag == m.ModelTag() 93 }, nil 94 } 95 96 config, err := st.ControllerConfig() 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 sender := senderFactory(config.MeteringURL() + "/metrics") 101 if err != nil { 102 return nil, errors.Trace(err) 103 } 104 return &MetricsManagerAPI{ 105 state: st, 106 pool: pool, 107 model: m, 108 accessModel: accessModel, 109 clock: clock, 110 sender: sender, 111 }, nil 112 } 113 114 // CleanupOldMetrics removes old metrics from the collection. 115 // The single arg params is expected to contain and model uuid. 116 // Even though the call will delete all metrics across models 117 // it serves to validate that the connection has access to at least one model. 118 func (api *MetricsManagerAPI) CleanupOldMetrics(args params.Entities) (params.ErrorResults, error) { 119 result := params.ErrorResults{ 120 Results: make([]params.ErrorResult, len(args.Entities)), 121 } 122 if len(args.Entities) == 0 { 123 return result, nil 124 } 125 canAccess, err := api.accessModel() 126 if err != nil { 127 return result, err 128 } 129 for i, arg := range args.Entities { 130 tag, err := names.ParseModelTag(arg.Tag) 131 if err != nil { 132 result.Results[i].Error = common.ServerError(common.ErrPerm) 133 continue 134 } 135 if !canAccess(tag) { 136 result.Results[i].Error = common.ServerError(common.ErrPerm) 137 continue 138 } 139 modelState, release, err := api.getModelState(tag) 140 if err != nil { 141 result.Results[i].Error = common.ServerError(err) 142 continue 143 } 144 defer release() 145 146 err = modelState.CleanupOldMetrics() 147 if err != nil { 148 err = errors.Annotatef(err, "failed to cleanup old metrics for %s", tag) 149 result.Results[i].Error = common.ServerError(err) 150 } 151 } 152 return result, nil 153 } 154 155 // AddJujuMachineMetrics adds a metric that counts the number of 156 // non-container machines in the current model. 157 func (api *MetricsManagerAPI) AddJujuMachineMetrics() error { 158 sla, err := api.state.SLACredential() 159 if err != nil { 160 return errors.Trace(err) 161 } 162 if len(sla) == 0 { 163 return nil 164 } 165 allMachines, err := api.state.AllMachines() 166 if err != nil { 167 return errors.Trace(err) 168 } 169 machineCount := 0 170 osMachineCount := map[os.OSType]int{} 171 for _, machine := range allMachines { 172 ct := machine.ContainerType() 173 if ct == instance.NONE || ct == "" { 174 machineCount++ 175 osType, err := series.GetOSFromSeries(machine.Series()) 176 if err != nil { 177 logger.Warningf("failed to resolve OS name for series %q: %v", machine.Series(), err) 178 osType = os.Unknown 179 } 180 osMachineCount[osType] = osMachineCount[osType] + 1 181 } 182 } 183 t := clock.WallClock.Now() 184 metrics := []state.Metric{{ 185 Key: "juju-machines", 186 Value: fmt.Sprintf("%d", machineCount), 187 Time: t, 188 }} 189 for osType, osMachineCount := range osMachineCount { 190 osName := strings.ToLower(osType.String()) 191 metrics = append(metrics, state.Metric{ 192 Key: "juju-" + osName + "-machines", 193 Value: fmt.Sprintf("%d", osMachineCount), 194 Time: t, 195 }) 196 } 197 metricUUID, err := utils.NewUUID() 198 if err != nil { 199 return errors.Trace(err) 200 } 201 _, err = api.state.AddModelMetrics(state.ModelBatchParam{ 202 UUID: metricUUID.String(), 203 Created: t, 204 Metrics: metrics, 205 }) 206 return err 207 } 208 209 // SendMetrics will send any unsent metrics onto the metric collection service. 210 func (api *MetricsManagerAPI) SendMetrics(args params.Entities) (params.ErrorResults, error) { 211 if err := api.AddJujuMachineMetrics(); err != nil { 212 logger.Warningf("failed to add juju-machines metrics: %v", err) 213 } 214 result := params.ErrorResults{ 215 Results: make([]params.ErrorResult, len(args.Entities)), 216 } 217 if len(args.Entities) == 0 { 218 return result, nil 219 } 220 canAccess, err := api.accessModel() 221 if err != nil { 222 return result, err 223 } 224 for i, arg := range args.Entities { 225 tag, err := names.ParseModelTag(arg.Tag) 226 if err != nil { 227 result.Results[i].Error = common.ServerError(err) 228 continue 229 } 230 if !canAccess(tag) { 231 result.Results[i].Error = common.ServerError(common.ErrPerm) 232 continue 233 } 234 modelState, release, err := api.getModelState(tag) 235 if err != nil { 236 result.Results[i].Error = common.ServerError(err) 237 continue 238 } 239 defer release() 240 241 txVendorMetrics, err := transmitVendorMetrics(api.model) 242 if err != nil { 243 result.Results[i].Error = common.ServerError(err) 244 continue 245 } 246 247 model, err := modelState.Model() 248 if err != nil { 249 return result, errors.Trace(err) 250 } 251 err = metricsender.SendMetrics(modelBackend{modelState, model}, api.sender, api.clock, maxBatchesPerSend, txVendorMetrics) 252 if err != nil { 253 err = errors.Annotatef(err, "failed to send metrics for %s", tag) 254 logger.Warningf("%v", err) 255 result.Results[i].Error = common.ServerError(err) 256 continue 257 } 258 } 259 return result, nil 260 } 261 262 func (api *MetricsManagerAPI) getModelState(tag names.Tag) (*state.State, func() bool, error) { 263 if tag == api.model.ModelTag() { 264 return api.state, func() bool { return false }, nil 265 } 266 st, err := api.pool.Get(tag.Id()) 267 if err != nil { 268 return nil, nil, errors.Annotatef(err, "failed to access state for %s", tag) 269 } 270 return st.State, st.Release, nil 271 } 272 273 func transmitVendorMetrics(m *state.Model) (bool, error) { 274 cfg, err := m.ModelConfig() 275 if err != nil { 276 return false, errors.Annotatef(err, "failed to get model config for %s", m.ModelTag()) 277 } 278 return cfg.TransmitVendorMetrics(), nil 279 }