github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/apiserver/environmentmanager/environmentmanager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The environmentmanager package defines an API end point for functions
     5  // dealing with envionments.  Creating, listing and sharing environments.
     6  package environmentmanager
     7  
     8  import (
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/feature"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/version"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.apiserver.environmentmanager")
    26  
    27  func init() {
    28  	common.RegisterStandardFacadeForFeature("EnvironmentManager", 1, NewEnvironmentManagerAPI, feature.JES)
    29  }
    30  
    31  // EnvironmentManager defines the methods on the environmentmanager API end
    32  // point.
    33  type EnvironmentManager interface {
    34  	ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error)
    35  	CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error)
    36  	ListEnvironments(user params.Entity) (params.UserEnvironmentList, error)
    37  }
    38  
    39  // EnvironmentManagerAPI implements the environment manager interface and is
    40  // the concrete implementation of the api end point.
    41  type EnvironmentManagerAPI struct {
    42  	state       stateInterface
    43  	authorizer  common.Authorizer
    44  	toolsFinder *common.ToolsFinder
    45  }
    46  
    47  var _ EnvironmentManager = (*EnvironmentManagerAPI)(nil)
    48  
    49  // NewEnvironmentManagerAPI creates a new api server endpoint for managing
    50  // environments.
    51  func NewEnvironmentManagerAPI(
    52  	st *state.State,
    53  	resources *common.Resources,
    54  	authorizer common.Authorizer,
    55  ) (*EnvironmentManagerAPI, error) {
    56  	if !authorizer.AuthClient() {
    57  		return nil, common.ErrPerm
    58  	}
    59  
    60  	urlGetter := common.NewToolsURLGetter(st.EnvironUUID(), st)
    61  	return &EnvironmentManagerAPI{
    62  		state:       getState(st),
    63  		authorizer:  authorizer,
    64  		toolsFinder: common.NewToolsFinder(st, st, urlGetter),
    65  	}, nil
    66  }
    67  
    68  // authCheck checks if the user is acting on their own behalf, or if they
    69  // are an administrator acting on behalf of another user.
    70  func (em *EnvironmentManagerAPI) authCheck(user names.UserTag) error {
    71  	// Since we know this is a user tag (because AuthClient is true),
    72  	// we just do the type assertion to the UserTag.
    73  	apiUser, _ := em.authorizer.GetAuthTag().(names.UserTag)
    74  	isAdmin, err := em.state.IsSystemAdministrator(apiUser)
    75  	if err != nil {
    76  		return errors.Trace(err)
    77  	}
    78  	if isAdmin {
    79  		logger.Tracef("%q is a system admin", apiUser.Username())
    80  		return nil
    81  	}
    82  
    83  	// We can't just compare the UserTags themselves as the provider part
    84  	// may be unset, and gets replaced with 'local'. We must compare against
    85  	// the Username of the user tag.
    86  	if apiUser.Username() == user.Username() {
    87  		return nil
    88  	}
    89  	return common.ErrPerm
    90  }
    91  
    92  // ConfigSource describes a type that is able to provide config.
    93  // Abstracted primarily for testing.
    94  type ConfigSource interface {
    95  	Config() (*config.Config, error)
    96  }
    97  
    98  var configValuesFromStateServer = []string{
    99  	"type",
   100  	"ca-cert",
   101  	"state-port",
   102  	"api-port",
   103  	"syslog-port",
   104  	"rsyslog-ca-cert",
   105  	"rsyslog-ca-key",
   106  }
   107  
   108  // ConfigSkeleton returns config values to be used as a starting point for the
   109  // API caller to construct a valid environment specific config.  The provider
   110  // and region params are there for future use, and current behaviour expects
   111  // both of these to be empty.
   112  func (em *EnvironmentManagerAPI) ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) {
   113  	var result params.EnvironConfigResult
   114  	if args.Provider != "" {
   115  		return result, errors.NotValidf("provider value %q", args.Provider)
   116  	}
   117  	if args.Region != "" {
   118  		return result, errors.NotValidf("region value %q", args.Region)
   119  	}
   120  
   121  	stateServerEnv, err := em.state.StateServerEnvironment()
   122  	if err != nil {
   123  		return result, errors.Trace(err)
   124  	}
   125  
   126  	config, err := em.configSkeleton(stateServerEnv)
   127  	if err != nil {
   128  		return result, errors.Trace(err)
   129  	}
   130  
   131  	result.Config = config
   132  	return result, nil
   133  }
   134  
   135  func (em *EnvironmentManagerAPI) restrictedProviderFields(providerType string) ([]string, error) {
   136  	provider, err := environs.Provider(providerType)
   137  	if err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  
   141  	var fields []string
   142  	fields = append(fields, configValuesFromStateServer...)
   143  	fields = append(fields, provider.RestrictedConfigAttributes()...)
   144  	return fields, nil
   145  }
   146  
   147  func (em *EnvironmentManagerAPI) configSkeleton(source ConfigSource) (map[string]interface{}, error) {
   148  	baseConfig, err := source.Config()
   149  	if err != nil {
   150  		return nil, errors.Trace(err)
   151  	}
   152  	baseMap := baseConfig.AllAttrs()
   153  
   154  	fields, err := em.restrictedProviderFields(baseConfig.Type())
   155  	if err != nil {
   156  		return nil, errors.Trace(err)
   157  	}
   158  
   159  	var result = make(map[string]interface{})
   160  	for _, field := range fields {
   161  		if value, found := baseMap[field]; found {
   162  			result[field] = value
   163  		}
   164  	}
   165  	return result, nil
   166  }
   167  
   168  func (em *EnvironmentManagerAPI) checkVersion(cfg map[string]interface{}) error {
   169  	// If there is no agent-version specified, use the current version.
   170  	// otherwise we need to check for tools
   171  	value, found := cfg["agent-version"]
   172  	if !found {
   173  		cfg["agent-version"] = version.Current.Number.String()
   174  		return nil
   175  	}
   176  	valuestr, ok := value.(string)
   177  	if !ok {
   178  		return errors.Errorf("agent-version must be a string but has type '%T'", value)
   179  	}
   180  	num, err := version.Parse(valuestr)
   181  	if err != nil {
   182  		return errors.Trace(err)
   183  	}
   184  	if comp := num.Compare(version.Current.Number); comp > 0 {
   185  		return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current.Number)
   186  	} else if comp < 0 {
   187  		// Look to see if we have tools available for that version.
   188  		// Obviously if the version is the same, we have the tools available.
   189  		list, err := em.toolsFinder.FindTools(params.FindToolsParams{
   190  			Number: num,
   191  		})
   192  		if err != nil {
   193  			return errors.Trace(err)
   194  		}
   195  		logger.Tracef("found tools: %#v", list)
   196  		if len(list.List) == 0 {
   197  			return errors.Errorf("no tools found for version %s", num)
   198  		}
   199  	}
   200  	return nil
   201  }
   202  
   203  func (em *EnvironmentManagerAPI) validConfig(attrs map[string]interface{}) (*config.Config, error) {
   204  	cfg, err := config.New(config.UseDefaults, attrs)
   205  	if err != nil {
   206  		return nil, errors.Annotate(err, "creating config from values failed")
   207  	}
   208  	provider, err := environs.Provider(cfg.Type())
   209  	if err != nil {
   210  		return nil, errors.Trace(err)
   211  	}
   212  	cfg, err = provider.PrepareForCreateEnvironment(cfg)
   213  	if err != nil {
   214  		return nil, errors.Trace(err)
   215  	}
   216  	cfg, err = provider.Validate(cfg, nil)
   217  	if err != nil {
   218  		return nil, errors.Annotate(err, "provider validation failed")
   219  	}
   220  	return cfg, nil
   221  }
   222  
   223  func (em *EnvironmentManagerAPI) newEnvironmentConfig(args params.EnvironmentCreateArgs, source ConfigSource) (*config.Config, error) {
   224  	// For now, we just smash to the two maps together as we store
   225  	// the account values and the environment config together in the
   226  	// *config.Config instance.
   227  	joint := make(map[string]interface{})
   228  	for key, value := range args.Config {
   229  		joint[key] = value
   230  	}
   231  	// Account info overrides any config values.
   232  	for key, value := range args.Account {
   233  		joint[key] = value
   234  	}
   235  	if _, found := joint["uuid"]; found {
   236  		return nil, errors.New("uuid is generated, you cannot specify one")
   237  	}
   238  	baseConfig, err := source.Config()
   239  	if err != nil {
   240  		return nil, errors.Trace(err)
   241  	}
   242  	baseMap := baseConfig.AllAttrs()
   243  	fields, err := em.restrictedProviderFields(baseConfig.Type())
   244  	if err != nil {
   245  		return nil, errors.Trace(err)
   246  	}
   247  	// Before comparing any values, we need to push the config through
   248  	// the provider validation code.  One of the reasons for this is that
   249  	// numbers being serialized through JSON get turned into float64. The
   250  	// schema code used in config will convert these back into integers.
   251  	// However, before we can create a valid config, we need to make sure
   252  	// we copy across fields from the main config that aren't there.
   253  	for _, field := range fields {
   254  		if _, found := joint[field]; !found {
   255  			if baseValue, found := baseMap[field]; found {
   256  				joint[field] = baseValue
   257  			}
   258  		}
   259  	}
   260  
   261  	cfg, err := em.validConfig(joint)
   262  	if err != nil {
   263  		return nil, errors.Trace(err)
   264  	}
   265  	attrs := cfg.AllAttrs()
   266  	// Any values that would normally be copied from the state server
   267  	// config can also be defined, but if they differ from the state server
   268  	// values, an error is returned.
   269  	for _, field := range fields {
   270  		if value, found := attrs[field]; found {
   271  			if serverValue := baseMap[field]; value != serverValue {
   272  				return nil, errors.Errorf(
   273  					"specified %s \"%v\" does not match apiserver \"%v\"",
   274  					field, value, serverValue)
   275  			}
   276  		}
   277  	}
   278  	if err := em.checkVersion(attrs); err != nil {
   279  		return nil, errors.Trace(err)
   280  	}
   281  
   282  	// Generate the UUID for the server.
   283  	uuid, err := utils.NewUUID()
   284  	if err != nil {
   285  		return nil, errors.Annotate(err, "failed to generate environment uuid")
   286  	}
   287  	attrs["uuid"] = uuid.String()
   288  
   289  	return em.validConfig(attrs)
   290  }
   291  
   292  // CreateEnvironment creates a new environment using the account and
   293  // environment config specified in the args.
   294  func (em *EnvironmentManagerAPI) CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) {
   295  	result := params.Environment{}
   296  	// Get the state server environment first. We need it both for the state
   297  	// server owner and the ability to get the config.
   298  	stateServerEnv, err := em.state.StateServerEnvironment()
   299  	if err != nil {
   300  		return result, errors.Trace(err)
   301  	}
   302  
   303  	ownerTag, err := names.ParseUserTag(args.OwnerTag)
   304  	if err != nil {
   305  		return result, errors.Trace(err)
   306  	}
   307  
   308  	// Any user is able to create themselves an environment (until real fine
   309  	// grain permissions are available), and admins (the creator of the state
   310  	// server environment) are able to create environments for other people.
   311  	err = em.authCheck(ownerTag)
   312  	if err != nil {
   313  		return result, errors.Trace(err)
   314  	}
   315  
   316  	newConfig, err := em.newEnvironmentConfig(args, stateServerEnv)
   317  	if err != nil {
   318  		return result, errors.Trace(err)
   319  	}
   320  	// NOTE: check the agent-version of the config, and if it is > the current
   321  	// version, it is not supported, also check existing tools, and if we don't
   322  	// have tools for that version, also die.
   323  	env, st, err := em.state.NewEnvironment(newConfig, ownerTag)
   324  	if err != nil {
   325  		return result, errors.Annotate(err, "failed to create new environment")
   326  	}
   327  	defer st.Close()
   328  
   329  	result.Name = env.Name()
   330  	result.UUID = env.UUID()
   331  	result.OwnerTag = env.Owner().String()
   332  
   333  	return result, nil
   334  }
   335  
   336  // ListEnvironments returns the environments that the specified user
   337  // has access to in the current server.  Only that state server owner
   338  // can list environments for any user (at this stage).  Other users
   339  // can only ask about their own environments.
   340  func (em *EnvironmentManagerAPI) ListEnvironments(user params.Entity) (params.UserEnvironmentList, error) {
   341  	result := params.UserEnvironmentList{}
   342  
   343  	userTag, err := names.ParseUserTag(user.Tag)
   344  	if err != nil {
   345  		return result, errors.Trace(err)
   346  	}
   347  
   348  	err = em.authCheck(userTag)
   349  	if err != nil {
   350  		return result, errors.Trace(err)
   351  	}
   352  
   353  	environments, err := em.state.EnvironmentsForUser(userTag)
   354  	if err != nil {
   355  		return result, errors.Trace(err)
   356  	}
   357  
   358  	for _, env := range environments {
   359  		var lastConn *time.Time
   360  		userLastConn, err := env.LastConnection()
   361  		if err != nil {
   362  			if !state.IsNeverConnectedError(err) {
   363  				return result, errors.Trace(err)
   364  			}
   365  		} else {
   366  			lastConn = &userLastConn
   367  		}
   368  		result.UserEnvironments = append(result.UserEnvironments, params.UserEnvironment{
   369  			Environment: params.Environment{
   370  				Name:     env.Name(),
   371  				UUID:     env.UUID(),
   372  				OwnerTag: env.Owner().String(),
   373  			},
   374  			LastConnection: lastConn,
   375  		})
   376  		logger.Debugf("list env: %s, %s, %s", env.Name(), env.UUID(), env.Owner())
   377  	}
   378  
   379  	return result, nil
   380  }