github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/configstate/configcore/vitality.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nomanagers
     3  
     4  /*
     5   * Copyright (C) 2020 Canonical Ltd
     6   *
     7   * This program is free software: you can redistribute it and/or modify
     8   * it under the terms of the GNU General Public License version 3 as
     9   * published by the Free Software Foundation.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   *
    19   */
    20  
    21  package configcore
    22  
    23  import (
    24  	"fmt"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/overlord/configstate/config"
    28  	"github.com/snapcore/snapd/overlord/snapstate"
    29  	"github.com/snapcore/snapd/overlord/state"
    30  	"github.com/snapcore/snapd/progress"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/snap/naming"
    33  	"github.com/snapcore/snapd/timings"
    34  	"github.com/snapcore/snapd/wrappers"
    35  )
    36  
    37  const vitalityOpt = "resilience.vitality-hint"
    38  
    39  func init() {
    40  	// add supported configuration of this module
    41  	supportedConfigurations["core."+vitalityOpt] = true
    42  }
    43  
    44  func handleVitalityConfiguration(tr config.Conf, opts *fsOnlyContext) error {
    45  	var pristineVitalityStr, newVitalityStr string
    46  
    47  	if err := tr.GetPristine("core", vitalityOpt, &pristineVitalityStr); err != nil && !config.IsNoOption(err) {
    48  		return err
    49  	}
    50  	if err := tr.Get("core", vitalityOpt, &newVitalityStr); err != nil && !config.IsNoOption(err) {
    51  		return err
    52  	}
    53  	if pristineVitalityStr == newVitalityStr {
    54  		return nil
    55  	}
    56  
    57  	st := tr.State()
    58  	st.Lock()
    59  	defer st.Unlock()
    60  
    61  	oldVitalityMap := map[string]int{}
    62  	newVitalityMap := map[string]int{}
    63  	// assign "0" (delete) rank to all old entries
    64  	for i, instanceName := range strings.Split(pristineVitalityStr, ",") {
    65  		oldVitalityMap[instanceName] = i + 1
    66  		newVitalityMap[instanceName] = 0
    67  	}
    68  	// build rank of the new entries
    69  	for i, instanceName := range strings.Split(newVitalityStr, ",") {
    70  		newVitalityMap[instanceName] = i + 1
    71  	}
    72  
    73  	for instanceName, rank := range newVitalityMap {
    74  		var snapst snapstate.SnapState
    75  		err := snapstate.Get(st, instanceName, &snapst)
    76  		// not installed, vitality-score will applied when the snap
    77  		// gets installed
    78  		if err == state.ErrNoState {
    79  			continue
    80  		}
    81  		if err != nil {
    82  			return err
    83  		}
    84  		// not active, vitality-score will applied when the snap
    85  		// becomes active
    86  		if !snapst.Active {
    87  			continue
    88  		}
    89  		info, err := snapst.CurrentInfo()
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		// nothing to do if rank is unchanged
    95  		if oldVitalityMap[instanceName] == newVitalityMap[instanceName] {
    96  			continue
    97  		}
    98  
    99  		// TODO: this should become some kind of Ensure*
   100  		// method in wrappers
   101  		disabledSvcs, err := wrappers.QueryDisabledServices(info, progress.Null)
   102  		if err != nil {
   103  			return err
   104  		}
   105  
   106  		// rank changed, rewrite/restart services
   107  		for _, app := range info.Apps {
   108  			if !app.IsService() {
   109  				continue
   110  			}
   111  
   112  			opts := &wrappers.AddSnapServicesOptions{VitalityRank: rank}
   113  			if err := wrappers.AddSnapServices(info, disabledSvcs, opts, progress.Null); err != nil {
   114  				return err
   115  			}
   116  		}
   117  		// XXX: copied from handlers.go:startSnapServices()
   118  		svcs := info.Services()
   119  		startupOrdered, err := snap.SortServices(svcs)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		tm := timings.New(nil)
   124  		if err = wrappers.StartServices(startupOrdered, nil, nil, progress.Null, tm); err != nil {
   125  			return err
   126  		}
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  func validateVitalitySettings(tr config.Conf) error {
   133  	option, err := coreCfg(tr, vitalityOpt)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	if option == "" {
   138  		return nil
   139  	}
   140  	vitalityHints := strings.Split(option, ",")
   141  	if len(vitalityHints) > 100 {
   142  		return fmt.Errorf("cannot set more than 100 snaps in %q: got %v", vitalityOpt, len(vitalityHints))
   143  	}
   144  	for _, instanceName := range vitalityHints {
   145  		if err := naming.ValidateInstance(instanceName); err != nil {
   146  			return fmt.Errorf("cannot set %q: %v", vitalityOpt, err)
   147  		}
   148  		// The "snapd" snap is always at OOMScoreAdjust=-900.
   149  		if instanceName == "snapd" {
   150  			return fmt.Errorf("cannot set %q: snapd snap vitality cannot be changed", vitalityOpt)
   151  		}
   152  	}
   153  
   154  	return nil
   155  }