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  }