github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/machinemanager/machinemanager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machinemanager
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/os"
    12  	"github.com/juju/os/series"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/common/storagecommon"
    17  	"github.com/juju/juju/apiserver/facade"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/core/instance"
    20  	"github.com/juju/juju/core/status"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/permission"
    24  	"github.com/juju/juju/state"
    25  )
    26  
    27  var logger = loggo.GetLogger("juju.apiserver.machinemanager")
    28  
    29  // MachineManagerAPI provides access to the MachineManager API facade.
    30  type MachineManagerAPI struct {
    31  	st            Backend
    32  	storageAccess storageInterface
    33  	pool          Pool
    34  	authorizer    facade.Authorizer
    35  	check         *common.BlockChecker
    36  	resources     facade.Resources
    37  
    38  	modelTag    names.ModelTag
    39  	callContext context.ProviderCallContext
    40  }
    41  
    42  // NewFacade create a new server-side MachineManager API facade. This
    43  // is used for facade registration.
    44  func NewFacade(ctx facade.Context) (*MachineManagerAPI, error) {
    45  	st := ctx.State()
    46  	model, err := st.Model()
    47  	if err != nil {
    48  		return nil, errors.Trace(err)
    49  	}
    50  	backend := &stateShim{State: st}
    51  	storageAccess, err := getStorageState(st)
    52  	if err != nil {
    53  		return nil, errors.Trace(err)
    54  	}
    55  	pool := &poolShim{ctx.StatePool()}
    56  	return NewMachineManagerAPI(backend, storageAccess, pool, ctx.Auth(), model.ModelTag(), state.CallContext(st), ctx.Resources())
    57  }
    58  
    59  // Version 4 of MachineManagerAPI
    60  type MachineManagerAPIV4 struct {
    61  	*MachineManagerAPIV5
    62  }
    63  
    64  // Version 5 of Machine Manager API.
    65  // Adds CreateUpgradeSeriesLock and removes UpdateMachineSeries.
    66  type MachineManagerAPIV5 struct {
    67  	*MachineManagerAPI
    68  }
    69  
    70  // NewFacadeV4 creates a new server-side MachineManager API facade.
    71  func NewFacadeV4(ctx facade.Context) (*MachineManagerAPIV4, error) {
    72  	machineManagerAPIV5, err := NewFacadeV5(ctx)
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  	return &MachineManagerAPIV4{machineManagerAPIV5}, nil
    77  }
    78  
    79  // NewFacadeV5 creates a new server-side MachineManager API facade.
    80  func NewFacadeV5(ctx facade.Context) (*MachineManagerAPIV5, error) {
    81  	machineManagerAPI, err := NewFacade(ctx)
    82  	if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	return &MachineManagerAPIV5{machineManagerAPI}, nil
    86  }
    87  
    88  // NewMachineManagerAPI creates a new server-side MachineManager API facade.
    89  func NewMachineManagerAPI(
    90  	backend Backend,
    91  	storageAccess storageInterface,
    92  	pool Pool,
    93  	auth facade.Authorizer,
    94  	modelTag names.ModelTag,
    95  	callCtx context.ProviderCallContext,
    96  	resources facade.Resources,
    97  ) (*MachineManagerAPI, error) {
    98  	if !auth.AuthClient() {
    99  		return nil, common.ErrPerm
   100  	}
   101  	return &MachineManagerAPI{
   102  		st:            backend,
   103  		storageAccess: storageAccess,
   104  		pool:          pool,
   105  		authorizer:    auth,
   106  		check:         common.NewBlockChecker(backend),
   107  		modelTag:      modelTag,
   108  		callContext:   callCtx,
   109  		resources:     resources,
   110  	}, nil
   111  }
   112  
   113  func (mm *MachineManagerAPI) checkCanWrite() error {
   114  	return mm.checkAccess(permission.WriteAccess)
   115  }
   116  
   117  func (mm *MachineManagerAPI) checkCanRead() error {
   118  	return mm.checkAccess(permission.ReadAccess)
   119  }
   120  
   121  func (mm *MachineManagerAPI) checkAccess(access permission.Access) error {
   122  	canAccess, err := mm.authorizer.HasPermission(access, mm.modelTag)
   123  	if err != nil {
   124  		return errors.Trace(err)
   125  	}
   126  	if !canAccess {
   127  		return common.ErrPerm
   128  	}
   129  	return nil
   130  }
   131  
   132  // AddMachines adds new machines with the supplied parameters.
   133  func (mm *MachineManagerAPI) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) {
   134  	results := params.AddMachinesResults{
   135  		Machines: make([]params.AddMachinesResult, len(args.MachineParams)),
   136  	}
   137  	if err := mm.checkCanWrite(); err != nil {
   138  		return results, err
   139  	}
   140  	if err := mm.check.ChangeAllowed(); err != nil {
   141  		return results, errors.Trace(err)
   142  	}
   143  	for i, p := range args.MachineParams {
   144  		m, err := mm.addOneMachine(p)
   145  		results.Machines[i].Error = common.ServerError(err)
   146  		if err == nil {
   147  			results.Machines[i].Machine = m.Id()
   148  		}
   149  	}
   150  	return results, nil
   151  }
   152  
   153  func (mm *MachineManagerAPI) addOneMachine(p params.AddMachineParams) (*state.Machine, error) {
   154  	if p.ParentId != "" && p.ContainerType == "" {
   155  		return nil, fmt.Errorf("parent machine specified without container type")
   156  	}
   157  	if p.ContainerType != "" && p.Placement != nil {
   158  		return nil, fmt.Errorf("container type and placement are mutually exclusive")
   159  	}
   160  	if p.Placement != nil {
   161  		// Extract container type and parent from container placement directives.
   162  		containerType, err := instance.ParseContainerType(p.Placement.Scope)
   163  		if err == nil {
   164  			p.ContainerType = containerType
   165  			p.ParentId = p.Placement.Directive
   166  			p.Placement = nil
   167  		}
   168  	}
   169  
   170  	if p.ContainerType != "" || p.Placement != nil {
   171  		// Guard against dubious client by making sure that
   172  		// the following attributes can only be set when we're
   173  		// not using placement.
   174  		p.InstanceId = ""
   175  		p.Nonce = ""
   176  		p.HardwareCharacteristics = instance.HardwareCharacteristics{}
   177  		p.Addrs = nil
   178  	}
   179  
   180  	if p.Series == "" {
   181  		model, err := mm.st.Model()
   182  		if err != nil {
   183  			return nil, errors.Trace(err)
   184  		}
   185  		conf, err := model.Config()
   186  		if err != nil {
   187  			return nil, errors.Trace(err)
   188  		}
   189  		p.Series = config.PreferredSeries(conf)
   190  	}
   191  
   192  	var placementDirective string
   193  	if p.Placement != nil {
   194  		model, err := mm.st.Model()
   195  		if err != nil {
   196  			return nil, errors.Trace(err)
   197  		}
   198  		// For 1.21 we should support both UUID and name, and with 1.22
   199  		// just support UUID
   200  		if p.Placement.Scope != model.Name() && p.Placement.Scope != model.UUID() {
   201  			return nil, fmt.Errorf("invalid model name %q", p.Placement.Scope)
   202  		}
   203  		placementDirective = p.Placement.Directive
   204  	}
   205  
   206  	volumes := make([]state.HostVolumeParams, 0, len(p.Disks))
   207  	for _, cons := range p.Disks {
   208  		if cons.Count == 0 {
   209  			return nil, errors.Errorf("invalid volume params: count not specified")
   210  		}
   211  		// Pool and Size are validated by AddMachineX.
   212  		volumeParams := state.VolumeParams{
   213  			Pool: cons.Pool,
   214  			Size: cons.Size,
   215  		}
   216  		volumeAttachmentParams := state.VolumeAttachmentParams{}
   217  		for i := uint64(0); i < cons.Count; i++ {
   218  			volumes = append(volumes, state.HostVolumeParams{
   219  				volumeParams, volumeAttachmentParams,
   220  			})
   221  		}
   222  	}
   223  
   224  	jobs, err := common.StateJobs(p.Jobs)
   225  	if err != nil {
   226  		return nil, errors.Trace(err)
   227  	}
   228  	template := state.MachineTemplate{
   229  		Series:                  p.Series,
   230  		Constraints:             p.Constraints,
   231  		Volumes:                 volumes,
   232  		InstanceId:              p.InstanceId,
   233  		Jobs:                    jobs,
   234  		Nonce:                   p.Nonce,
   235  		HardwareCharacteristics: p.HardwareCharacteristics,
   236  		Addresses:               params.NetworkAddresses(p.Addrs...),
   237  		Placement:               placementDirective,
   238  	}
   239  	if p.ContainerType == "" {
   240  		return mm.st.AddOneMachine(template)
   241  	}
   242  	if p.ParentId != "" {
   243  		return mm.st.AddMachineInsideMachine(template, p.ParentId, p.ContainerType)
   244  	}
   245  	return mm.st.AddMachineInsideNewMachine(template, template, p.ContainerType)
   246  }
   247  
   248  // DestroyMachine removes a set of machines from the model.
   249  func (mm *MachineManagerAPI) DestroyMachine(args params.Entities) (params.DestroyMachineResults, error) {
   250  	return mm.destroyMachine(args, false, false)
   251  }
   252  
   253  // ForceDestroyMachine forcibly removes a set of machines from the model.
   254  func (mm *MachineManagerAPI) ForceDestroyMachine(args params.Entities) (params.DestroyMachineResults, error) {
   255  	return mm.destroyMachine(args, true, false)
   256  }
   257  
   258  // DestroyMachineWithParams removes a set of machines from the model.
   259  func (mm *MachineManagerAPI) DestroyMachineWithParams(args params.DestroyMachinesParams) (params.DestroyMachineResults, error) {
   260  	entities := params.Entities{Entities: make([]params.Entity, len(args.MachineTags))}
   261  	for i, tag := range args.MachineTags {
   262  		entities.Entities[i].Tag = tag
   263  	}
   264  	return mm.destroyMachine(entities, args.Force, args.Keep)
   265  }
   266  
   267  func (mm *MachineManagerAPI) destroyMachine(args params.Entities, force, keep bool) (params.DestroyMachineResults, error) {
   268  	if err := mm.checkCanWrite(); err != nil {
   269  		return params.DestroyMachineResults{}, err
   270  	}
   271  	if err := mm.check.RemoveAllowed(); err != nil {
   272  		return params.DestroyMachineResults{}, err
   273  	}
   274  	destroyMachine := func(entity params.Entity) (*params.DestroyMachineInfo, error) {
   275  		machineTag, err := names.ParseMachineTag(entity.Tag)
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  		machine, err := mm.st.Machine(machineTag.Id())
   280  		if err != nil {
   281  			return nil, err
   282  		}
   283  		if keep {
   284  			logger.Infof("destroy machine %v but keep instance", machineTag.Id())
   285  			if err := machine.SetKeepInstance(keep); err != nil {
   286  				return nil, err
   287  			}
   288  		}
   289  		var info params.DestroyMachineInfo
   290  		units, err := machine.Units()
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		storageSeen := names.NewSet()
   295  		for _, unit := range units {
   296  			info.DestroyedUnits = append(
   297  				info.DestroyedUnits,
   298  				params.Entity{Tag: unit.UnitTag().String()},
   299  			)
   300  			storage, err := storagecommon.UnitStorage(mm.storageAccess, unit.UnitTag())
   301  			if err != nil {
   302  				return nil, err
   303  			}
   304  
   305  			// Filter out storage we've already seen. Shared
   306  			// storage may be attached to multiple units.
   307  			var unseen []state.StorageInstance
   308  			for _, storage := range storage {
   309  				storageTag := storage.StorageTag()
   310  				if storageSeen.Contains(storageTag) {
   311  					continue
   312  				}
   313  				storageSeen.Add(storageTag)
   314  				unseen = append(unseen, storage)
   315  			}
   316  			storage = unseen
   317  
   318  			destroyed, detached, err := storagecommon.ClassifyDetachedStorage(
   319  				mm.storageAccess.VolumeAccess(), mm.storageAccess.FilesystemAccess(), storage)
   320  			if err != nil {
   321  				return nil, err
   322  			}
   323  			info.DestroyedStorage = append(info.DestroyedStorage, destroyed...)
   324  			info.DetachedStorage = append(info.DetachedStorage, detached...)
   325  		}
   326  		destroy := machine.Destroy
   327  		if force {
   328  			destroy = machine.ForceDestroy
   329  		}
   330  		if err := destroy(); err != nil {
   331  			return nil, err
   332  		}
   333  		return &info, nil
   334  	}
   335  	results := make([]params.DestroyMachineResult, len(args.Entities))
   336  	for i, entity := range args.Entities {
   337  		info, err := destroyMachine(entity)
   338  		if err != nil {
   339  			results[i].Error = common.ServerError(err)
   340  			continue
   341  		}
   342  		results[i].Info = info
   343  	}
   344  	return params.DestroyMachineResults{results}, nil
   345  }
   346  
   347  // UpgradeSeriesValidate validates that the incoming arguments correspond to a
   348  // valid series upgrade for the target machine.
   349  // If they do, a list of the machine's current units is returned for use in
   350  // soliciting user confirmation of the command.
   351  func (mm *MachineManagerAPI) UpgradeSeriesValidate(
   352  	args params.UpdateSeriesArgs,
   353  ) (params.UpgradeSeriesUnitsResults, error) {
   354  	err := mm.checkCanRead()
   355  	if err != nil {
   356  		return params.UpgradeSeriesUnitsResults{}, err
   357  	}
   358  
   359  	results := make([]params.UpgradeSeriesUnitsResult, len(args.Args))
   360  	for i, arg := range args.Args {
   361  		tag := arg.Entity.Tag
   362  		machine, err := mm.machineFromTag(tag)
   363  		if err != nil {
   364  			results[i].Error = common.ServerError(err)
   365  			continue
   366  		}
   367  
   368  		if machine.IsManager() {
   369  			results[i].Error = common.ServerError(
   370  				errors.Errorf("%s is a controller and cannot be targeted for series upgrade", tag))
   371  			continue
   372  		}
   373  
   374  		err = mm.validateSeries(arg.Series, machine.Series(), tag)
   375  		if err != nil {
   376  			results[i].Error = common.ServerError(err)
   377  			continue
   378  		}
   379  
   380  		unitNames, err := mm.verifiedUnits(machine, arg.Series, arg.Force)
   381  		if err != nil {
   382  			results[i].Error = common.ServerError(err)
   383  			continue
   384  		}
   385  		results[i].UnitNames = unitNames
   386  	}
   387  
   388  	return params.UpgradeSeriesUnitsResults{Results: results}, nil
   389  }
   390  
   391  // UpgradeSeriesPrepare prepares a machine for a OS series upgrade.
   392  func (mm *MachineManagerAPI) UpgradeSeriesPrepare(args params.UpdateSeriesArg) (params.ErrorResult, error) {
   393  	if err := mm.checkCanWrite(); err != nil {
   394  		return params.ErrorResult{}, err
   395  	}
   396  	if err := mm.check.ChangeAllowed(); err != nil {
   397  		return params.ErrorResult{}, err
   398  	}
   399  	err := mm.upgradeSeriesPrepare(args)
   400  	if err != nil {
   401  		return params.ErrorResult{Error: common.ServerError(err)}, nil
   402  	}
   403  	return params.ErrorResult{}, nil
   404  }
   405  
   406  func (mm *MachineManagerAPI) upgradeSeriesPrepare(arg params.UpdateSeriesArg) error {
   407  	if arg.Series == "" {
   408  		return &params.Error{
   409  			Message: "series missing from args",
   410  			Code:    params.CodeBadRequest,
   411  		}
   412  	}
   413  	machineTag, err := names.ParseMachineTag(arg.Entity.Tag)
   414  	if err != nil {
   415  		return errors.Trace(err)
   416  	}
   417  	machine, err := mm.st.Machine(machineTag.Id())
   418  	if err != nil {
   419  		return errors.Trace(err)
   420  	}
   421  	unitNames, err := mm.verifiedUnits(machine, arg.Series, arg.Force)
   422  	if err != nil {
   423  		return errors.Trace(err)
   424  	}
   425  
   426  	if err = machine.CreateUpgradeSeriesLock(unitNames, arg.Series); err != nil {
   427  		// TODO 2018-06-28 managed series upgrade
   428  		// improve error handling based on error type, there will be cases where retrying
   429  		// the hooks is needed etc.
   430  		return errors.Trace(err)
   431  	}
   432  	defer func() {
   433  		if err != nil {
   434  			if err2 := machine.RemoveUpgradeSeriesLock(); err2 != nil {
   435  				err = errors.Annotatef(err, "%s occurred while cleaning up from", err2)
   436  			}
   437  		}
   438  	}()
   439  	return nil
   440  }
   441  
   442  // UpgradeSeriesComplete marks a machine as having completed a managed series upgrade.
   443  func (mm *MachineManagerAPI) UpgradeSeriesComplete(args params.UpdateSeriesArg) (params.ErrorResult, error) {
   444  	if err := mm.checkCanWrite(); err != nil {
   445  		return params.ErrorResult{}, err
   446  	}
   447  	if err := mm.check.ChangeAllowed(); err != nil {
   448  		return params.ErrorResult{}, err
   449  	}
   450  	if err := mm.check.ChangeAllowed(); err != nil {
   451  		return params.ErrorResult{}, err
   452  	}
   453  	err := mm.completeUpgradeSeries(args)
   454  	if err != nil {
   455  		return params.ErrorResult{Error: common.ServerError(err)}, nil
   456  	}
   457  
   458  	return params.ErrorResult{}, nil
   459  }
   460  
   461  func (mm *MachineManagerAPI) completeUpgradeSeries(arg params.UpdateSeriesArg) error {
   462  	machine, err := mm.machineFromTag(arg.Entity.Tag)
   463  	if err != nil {
   464  		return errors.Trace(err)
   465  	}
   466  	return machine.CompleteUpgradeSeries()
   467  }
   468  
   469  func (mm *MachineManagerAPI) removeUpgradeSeriesLock(arg params.UpdateSeriesArg) error {
   470  	machine, err := mm.machineFromTag(arg.Entity.Tag)
   471  	if err != nil {
   472  		return errors.Trace(err)
   473  	}
   474  	return machine.RemoveUpgradeSeriesLock()
   475  }
   476  
   477  // WatchUpgradeSeriesNotifications returns a watcher that fires on upgrade series events.
   478  func (mm *MachineManagerAPI) WatchUpgradeSeriesNotifications(args params.Entities) (params.NotifyWatchResults, error) {
   479  	err := mm.checkCanRead()
   480  	if err != nil {
   481  		return params.NotifyWatchResults{}, err
   482  	}
   483  	result := params.NotifyWatchResults{
   484  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   485  	}
   486  	for i, entity := range args.Entities {
   487  		tag, err := names.ParseTag(entity.Tag)
   488  		if err != nil {
   489  			result.Results[i].Error = common.ServerError(common.ErrPerm)
   490  			continue
   491  		}
   492  		watcherId := ""
   493  		machine, err := mm.st.Machine(tag.Id())
   494  		if err != nil {
   495  			result.Results[i].Error = common.ServerError(err)
   496  			continue
   497  		}
   498  		w, err := machine.WatchUpgradeSeriesNotifications()
   499  		if err != nil {
   500  			result.Results[i].Error = common.ServerError(err)
   501  			continue
   502  		}
   503  		watcherId = mm.resources.Register(w)
   504  		result.Results[i].NotifyWatcherId = watcherId
   505  	}
   506  	return result, nil
   507  }
   508  
   509  // GetUpgradeSeriesMessages returns all new messages associated with upgrade
   510  // series events. Messages that have already been retrieved once are not
   511  // returned by this method.
   512  func (mm *MachineManagerAPI) GetUpgradeSeriesMessages(args params.UpgradeSeriesNotificationParams) (params.StringsResults, error) {
   513  	if err := mm.checkCanRead(); err != nil {
   514  		return params.StringsResults{}, err
   515  	}
   516  	results := params.StringsResults{
   517  		Results: make([]params.StringsResult, len(args.Params)),
   518  	}
   519  	for i, param := range args.Params {
   520  		machine, err := mm.machineFromTag(param.Entity.Tag)
   521  		if err != nil {
   522  			err = errors.Trace(err)
   523  			results.Results[i].Error = common.ServerError(err)
   524  			continue
   525  		}
   526  		messages, finished, err := machine.GetUpgradeSeriesMessages()
   527  		if err != nil {
   528  			results.Results[i].Error = common.ServerError(err)
   529  			continue
   530  		}
   531  		if finished {
   532  			// If there are no more messages we stop the watcher resource.
   533  			err = mm.resources.Stop(param.WatcherId)
   534  			if err != nil {
   535  				results.Results[i].Error = common.ServerError(err)
   536  				continue
   537  			}
   538  		}
   539  		results.Results[i].Result = messages
   540  	}
   541  	return results, nil
   542  }
   543  
   544  func (mm *MachineManagerAPI) machineFromTag(tag string) (Machine, error) {
   545  	machineTag, err := names.ParseMachineTag(tag)
   546  	if err != nil {
   547  		return nil, errors.Trace(err)
   548  	}
   549  	machine, err := mm.st.Machine(machineTag.Id())
   550  	if err != nil {
   551  		return nil, errors.Trace(err)
   552  	}
   553  	return machine, nil
   554  }
   555  
   556  // verifiedUnits verifies that the machine units and their tree of subordinates
   557  // all support the input series. If not, an error is returned.
   558  // If they do, the agent statuses are checked to ensure that they are all in
   559  // the idle state i.e. not installing, running hooks, or needing intervention.
   560  // the final check is that the unit itself is not in an error state.
   561  func (mm *MachineManagerAPI) verifiedUnits(machine Machine, series string, force bool) ([]string, error) {
   562  	principals := machine.Principals()
   563  	units, err := machine.VerifyUnitsSeries(principals, series, force)
   564  	if err != nil {
   565  		return nil, errors.Trace(err)
   566  	}
   567  
   568  	unitNames := make([]string, len(units))
   569  	for i, u := range units {
   570  		agentStatus, err := u.AgentStatus()
   571  		if err != nil {
   572  			return nil, errors.Trace(err)
   573  		}
   574  		if agentStatus.Status != status.Idle {
   575  			return nil, errors.Errorf("unit %s is not ready to start a series upgrade; its agent status is: %q %s",
   576  				u.Name(), agentStatus.Status, agentStatus.Message)
   577  		}
   578  		unitStatus, err := u.Status()
   579  		if err != nil {
   580  			return nil, errors.Trace(err)
   581  		}
   582  		if unitStatus.Status == status.Error {
   583  			return nil, errors.Errorf("unit %s is not ready to start a series upgrade; its status is: \"error\" %s",
   584  				u.Name(), unitStatus.Message)
   585  		}
   586  
   587  		unitNames[i] = u.UnitTag().Id()
   588  	}
   589  	return unitNames, nil
   590  }
   591  
   592  // isSeriesLessThan returns a bool indicating whether the first argument's
   593  // version is lexicographically less than the second argument's, thus indicating
   594  // that the series represents an older version of the operating system. The
   595  // output is only valid for Ubuntu series.
   596  func isSeriesLessThan(series1, series2 string) (bool, error) {
   597  	version1, err := series.SeriesVersion(series1)
   598  	if err != nil {
   599  		return false, err
   600  	}
   601  	version2, err := series.SeriesVersion(series2)
   602  	if err != nil {
   603  		return false, err
   604  	}
   605  	return version2 > version1, nil
   606  }
   607  
   608  // DEPRECATED: UpdateMachineSeries returns an error.
   609  func (mm *MachineManagerAPIV4) UpdateMachineSeries(_ params.UpdateSeriesArgs) (params.ErrorResults, error) {
   610  	return params.ErrorResults{
   611  		Results: []params.ErrorResult{{
   612  			Error: common.ServerError(errors.New("UpdateMachineSeries is no longer supported")),
   613  		}},
   614  	}, nil
   615  }
   616  
   617  func (mm *MachineManagerAPI) validateSeries(argumentSeries, currentSeries string, machineTag string) error {
   618  	if argumentSeries == "" {
   619  		return &params.Error{
   620  			Message: "series missing from args",
   621  			Code:    params.CodeBadRequest,
   622  		}
   623  	}
   624  
   625  	opSys, err := series.GetOSFromSeries(argumentSeries)
   626  	if err != nil {
   627  		return errors.Trace(err)
   628  	}
   629  	if opSys != os.Ubuntu {
   630  		return errors.Errorf("series %q is from OS %q and is not a valid upgrade target",
   631  			argumentSeries, opSys.String())
   632  	}
   633  
   634  	opSys, err = series.GetOSFromSeries(currentSeries)
   635  	if err != nil {
   636  		return errors.Trace(err)
   637  	}
   638  	if opSys != os.Ubuntu {
   639  		return errors.Errorf("%s is running %s and is not valid for Ubuntu series upgrade",
   640  			machineTag, opSys.String())
   641  	}
   642  
   643  	if argumentSeries == currentSeries {
   644  		return errors.Errorf("%s is already running series %s", machineTag, argumentSeries)
   645  	}
   646  
   647  	isOlderSeries, err := isSeriesLessThan(argumentSeries, currentSeries)
   648  	if err != nil {
   649  		return errors.Trace(err)
   650  	}
   651  	if isOlderSeries {
   652  		return errors.Errorf("machine %s is running %s which is a newer series than %s.",
   653  			machineTag, currentSeries, argumentSeries)
   654  	}
   655  
   656  	return nil
   657  }