// processGet handles a charm file GET request after authentication. // It returns the bundle path, the requested file path (if any), whether the // default charm icon has been requested and an error. func (h *charmsHandler) processGet(r *http.Request, st *state.State) ( archivePath string, fileArg string, serveIcon bool, err error, ) { errRet := func(err error) (string, string, bool, error) { return "", "", false, err } query := r.URL.Query() // Retrieve and validate query parameters. curlString := query.Get("url") if curlString == "" { return errRet(errors.Errorf("expected url=CharmURL query argument")) } curl, err := charm.ParseURL(curlString) if err != nil { return errRet(errors.Trace(err)) } fileArg = query.Get("file") if fileArg != "" { fileArg = path.Clean(fileArg) } else if query.Get("icon") == "1" { serveIcon = true fileArg = "icon.svg" } // Ensure the working directory exists. tmpDir := filepath.Join(h.dataDir, "charm-get-tmp") if err = os.MkdirAll(tmpDir, 0755); err != nil { return errRet(errors.Annotate(err, "cannot create charms tmp directory")) } // Use the storage to retrieve and save the charm archive. storage := storage.NewStorage(st.ModelUUID(), st.MongoSession()) ch, err := st.Charm(curl) if err != nil { return errRet(errors.Annotate(err, "cannot get charm from state")) } reader, _, err := storage.Get(ch.StoragePath()) if err != nil { return errRet(errors.Annotate(err, "cannot get charm from model storage")) } defer reader.Close() charmFile, err := ioutil.TempFile(tmpDir, "charm") if err != nil { return errRet(errors.Annotate(err, "cannot create charm archive file")) } if _, err = io.Copy(charmFile, reader); err != nil { cleanupFile(charmFile) return errRet(errors.Annotate(err, "error processing charm archive download")) } charmFile.Close() return charmFile.Name(), fileArg, serveIcon, nil }
// StoreCharmArchive stores a charm archive in environment storage. func StoreCharmArchive(st *state.State, curl *charm.URL, ch charm.Charm, r io.Reader, size int64, sha256 string) error { storage := newStateStorage(st.ModelUUID(), st.MongoSession()) storagePath, err := charmArchiveStoragePath(curl) if err != nil { return errors.Annotate(err, "cannot generate charm archive name") } if err := storage.Put(storagePath, r, size); err != nil { return errors.Annotate(err, "cannot add charm to storage") } // Now update the charm data in state and mark it as no longer pending. _, err = st.UpdateUploadedCharm(ch, curl, storagePath, sha256) if err != nil { alreadyUploaded := err == state.ErrCharmRevisionAlreadyModified || errors.Cause(err) == state.ErrCharmRevisionAlreadyModified || state.IsCharmAlreadyUploadedError(err) if err := storage.Remove(storagePath); err != nil { if alreadyUploaded { logger.Errorf("cannot remove duplicated charm archive from storage: %v", err) } else { logger.Errorf("cannot remove unsuccessfully recorded charm archive from storage: %v", err) } } if alreadyUploaded { // Somebody else managed to upload and update the charm in // state before us. This is not an error. return nil } } return nil }
// TestingApiHandler gives you an ApiHandler that isn't connected to // anything real. It's enough to let test some basic functionality though. func TestingApiHandler(c *gc.C, srvSt, st *state.State) (*apiHandler, *common.Resources) { srv := &Server{ state: srvSt, tag: names.NewMachineTag("0"), } h, err := newApiHandler(srv, st, nil, nil, st.ModelUUID()) c.Assert(err, jc.ErrorIsNil) return h, h.getResources() }
// dyingEnvWorker is passed to NewModelWorkerManager in these tests. It // creates a fake Runner instance when envWorkerManager starts workers for a // dying or dead environment. func (s *suite) dyingEnvWorker(ssSt modelworkermanager.InitialState, st *state.State) (worker.Worker, error) { if s.startErr != nil { return nil, s.startErr } runner := &fakeRunner{ ssModelUUID: ssSt.ModelUUID(), modelUUID: st.ModelUUID(), } s.runnerC <- runner return runner, nil }
// getSecretKeyLoginResponsePayload returns the information required by the // client to login to the controller securely. func (h *registerUserHandler) getSecretKeyLoginResponsePayload( st *state.State, ) (*params.SecretKeyLoginResponsePayload, error) { if !st.IsController() { return nil, errors.New("state is not for a controller") } payload := params.SecretKeyLoginResponsePayload{ CACert: st.CACert(), ControllerUUID: st.ModelUUID(), } return &payload, nil }
// TestingAPIHandler gives you an APIHandler that isn't connected to // anything real. It's enough to let test some basic functionality though. func TestingAPIHandler(c *gc.C, srvSt, st *state.State) (*apiHandler, *common.Resources) { authCtxt, err := newAuthContext(srvSt) c.Assert(err, jc.ErrorIsNil) srv := &Server{ authCtxt: authCtxt, state: srvSt, tag: names.NewMachineTag("0"), } h, err := newAPIHandler(srv, st, nil, st.ModelUUID(), "testing.invalid:1234") c.Assert(err, jc.ErrorIsNil) return h, h.getResources() }
func addCharm(st *state.State, curl *charm.URL, ch charm.Charm) (*state.Charm, error) { var f *os.File name := charm.Quote(curl.String()) switch ch := ch.(type) { case *charm.CharmDir: var err error if f, err = ioutil.TempFile("", name); err != nil { return nil, err } defer os.Remove(f.Name()) defer f.Close() err = ch.ArchiveTo(f) if err != nil { return nil, fmt.Errorf("cannot bundle charm: %v", err) } if _, err := f.Seek(0, 0); err != nil { return nil, err } case *charm.CharmArchive: var err error if f, err = os.Open(ch.Path); err != nil { return nil, fmt.Errorf("cannot read charm bundle: %v", err) } defer f.Close() default: return nil, fmt.Errorf("unknown charm type %T", ch) } digest, size, err := utils.ReadSHA256(f) if err != nil { return nil, err } if _, err := f.Seek(0, 0); err != nil { return nil, err } stor := statestorage.NewStorage(st.ModelUUID(), st.MongoSession()) storagePath := fmt.Sprintf("/charms/%s-%s", curl.String(), digest) if err := stor.Put(storagePath, f, size); err != nil { return nil, fmt.Errorf("cannot put charm: %v", err) } info := state.CharmInfo{ Charm: ch, ID: curl, StoragePath: storagePath, SHA256: digest, } sch, err := st.AddCharm(info) if err != nil { return nil, fmt.Errorf("cannot add charm: %v", err) } return sch, nil }
func (s *imageSuite) storeFakeImage(c *gc.C, st *state.State, kind, series, arch string) { storage := st.ImageStorage() metadata := &imagestorage.Metadata{ ModelUUID: st.ModelUUID(), Kind: kind, Series: series, Arch: arch, Size: int64(len(testImageData)), SHA256: testImageChecksum, SourceURL: "http://path", } err := storage.AddImage(strings.NewReader(testImageData), metadata) c.Assert(err, gc.IsNil) }
// NewModelManagerAPI creates a new api server endpoint for managing // models. func NewModelManagerAPI( st *state.State, resources *common.Resources, authorizer common.Authorizer, ) (*ModelManagerAPI, error) { if !authorizer.AuthClient() { return nil, common.ErrPerm } urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st) return &ModelManagerAPI{ state: getState(st), authorizer: authorizer, toolsFinder: common.NewToolsFinder(st, st, urlGetter), }, nil }
// getSecretKeyLoginResponsePayload returns the information required by the // client to login to the controller securely. func (h *registerUserHandler) getSecretKeyLoginResponsePayload( st *state.State, userTag names.UserTag, ) (*params.SecretKeyLoginResponsePayload, error) { if !st.IsController() { return nil, errors.New("state is not for a controller") } mac, err := h.createLocalLoginMacaroon(userTag) if err != nil { return nil, errors.Trace(err) } payload := params.SecretKeyLoginResponsePayload{ CACert: st.CACert(), ControllerUUID: st.ModelUUID(), Macaroon: mac, } return &payload, nil }
// newBakeryService creates a new bakery.Service. func newBakeryService( st *state.State, store bakerystorage.ExpirableStorage, locator bakery.PublicKeyLocator, ) (*bakery.Service, *bakery.KeyPair, error) { key, err := bakery.GenerateKey() if err != nil { return nil, nil, errors.Annotate(err, "generating key for bakery service") } service, err := bakery.NewService(bakery.NewServiceParams{ Location: "juju model " + st.ModelUUID(), Store: store, Key: key, Locator: locator, }) if err != nil { return nil, nil, errors.Trace(err) } return service, key, nil }
// processGet handles a charm file GET request after authentication. // It returns the bundle path, the requested file path (if any) and an error. func (h *charmsHandler) processGet(r *http.Request, st *state.State) (string, string, error) { query := r.URL.Query() // Retrieve and validate query parameters. curlString := query.Get("url") if curlString == "" { return "", "", fmt.Errorf("expected url=CharmURL query argument") } curl, err := charm.ParseURL(curlString) if err != nil { return "", "", errors.Annotate(err, "cannot parse charm URL") } var filePath string file := query.Get("file") if file == "" { filePath = "" } else { filePath = path.Clean(file) } // Prepare the bundle directories. name := charm.Quote(curlString) charmArchivePath := filepath.Join( h.dataDir, "charm-get-cache", st.ModelUUID(), name+".zip", ) // Check if the charm archive is already in the cache. if _, err := os.Stat(charmArchivePath); os.IsNotExist(err) { // Download the charm archive and save it to the cache. if err = h.downloadCharm(st, curl, charmArchivePath); err != nil { return "", "", errors.Annotate(err, "unable to retrieve and save the charm") } } else if err != nil { return "", "", errors.Annotate(err, "cannot access the charms cache") } return charmArchivePath, filePath, nil }
// downloadCharm downloads the given charm name from the provider storage and // saves the corresponding zip archive to the given charmArchivePath. func (h *charmsHandler) downloadCharm(st *state.State, curl *charm.URL, charmArchivePath string) error { storage := storage.NewStorage(st.ModelUUID(), st.MongoSession()) ch, err := st.Charm(curl) if err != nil { return errors.Annotate(err, "cannot get charm from state") } // In order to avoid races, the archive is saved in a temporary file which // is then atomically renamed. The temporary file is created in the // charm cache directory so that we can safely assume the rename source and // target live in the same file system. cacheDir := filepath.Dir(charmArchivePath) if err = os.MkdirAll(cacheDir, 0755); err != nil { return errors.Annotate(err, "cannot create the charms cache") } tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") if err != nil { return errors.Annotate(err, "cannot create charm archive temp file") } defer tempCharmArchive.Close() // Use the storage to retrieve and save the charm archive. reader, _, err := storage.Get(ch.StoragePath()) if err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "cannot get charm from model storage") } defer reader.Close() if _, err = io.Copy(tempCharmArchive, reader); err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error processing charm archive download") } tempCharmArchive.Close() if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error renaming the charm archive") } return nil }
// StoreCharmArchive stores a charm archive in environment storage. func StoreCharmArchive(st *state.State, archive CharmArchive) error { storage := newStateStorage(st.ModelUUID(), st.MongoSession()) storagePath, err := charmArchiveStoragePath(archive.ID) if err != nil { return errors.Annotate(err, "cannot generate charm archive name") } if err := storage.Put(storagePath, archive.Data, archive.Size); err != nil { return errors.Annotate(err, "cannot add charm to storage") } info := state.CharmInfo{ Charm: archive.Charm, ID: archive.ID, StoragePath: storagePath, SHA256: archive.SHA256, Macaroon: archive.Macaroon, } // Now update the charm data in state and mark it as no longer pending. _, err = st.UpdateUploadedCharm(info) if err != nil { alreadyUploaded := err == state.ErrCharmRevisionAlreadyModified || errors.Cause(err) == state.ErrCharmRevisionAlreadyModified || state.IsCharmAlreadyUploadedError(err) if err := storage.Remove(storagePath); err != nil { if alreadyUploaded { logger.Errorf("cannot remove duplicated charm archive from storage: %v", err) } else { logger.Errorf("cannot remove unsuccessfully recorded charm archive from storage: %v", err) } } if alreadyUploaded { // Somebody else managed to upload and update the charm in // state before us. This is not an error. return nil } } return nil }
// newMacaroonAuth returns an authenticator that can authenticate // macaroon-based logins. This is just a helper function for authCtxt.macaroonAuth. func newMacaroonAuth(st *state.State) (*authentication.MacaroonAuthenticator, error) { envCfg, err := st.ModelConfig() if err != nil { return nil, errors.Annotate(err, "cannot get model config") } idURL := envCfg.IdentityURL() if idURL == "" { return nil, errMacaroonAuthNotConfigured } // The identity server has been configured, // so configure the bakery service appropriately. idPK := envCfg.IdentityPublicKey() if idPK == nil { // No public key supplied - retrieve it from the identity manager. idPK, err = httpbakery.PublicKeyForLocation(http.DefaultClient, idURL) if err != nil { return nil, errors.Annotate(err, "cannot get identity public key") } } svc, err := bakery.NewService( bakery.NewServiceParams{ Location: "juju model " + st.ModelUUID(), Locator: bakery.PublicKeyLocatorMap{ idURL: idPK, }, }, ) if err != nil { return nil, errors.Annotate(err, "cannot make bakery service") } var auth authentication.MacaroonAuthenticator auth.Service = svc auth.Macaroon, err = svc.NewMacaroon("api-login", nil, nil) if err != nil { return nil, errors.Annotate(err, "cannot make macaroon") } auth.IdentityLocation = idURL return &auth, nil }
func newClient(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*Client, error) { urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st) configGetter := stateenvirons.EnvironConfigGetter{st} statusSetter := common.NewStatusSetter(st, common.AuthAlways()) toolsFinder := common.NewToolsFinder(configGetter, st, urlGetter) newEnviron := func() (environs.Environ, error) { return environs.GetEnviron(configGetter, environs.New) } blockChecker := common.NewBlockChecker(st) modelConfigAPI, err := modelconfig.NewModelConfigAPI(st, authorizer) if err != nil { return nil, errors.Trace(err) } return NewClient( NewStateBackend(st), modelConfigAPI, resources, authorizer, statusSetter, toolsFinder, newEnviron, blockChecker, ) }
// Run initializes state for an environment. func (c *BootstrapCommand) Run(_ *cmd.Context) error { envCfg, err := config.New(config.NoDefaults, c.ControllerModelConfig) if err != nil { return err } err = c.ReadConfig("machine-0") if err != nil { return err } agentConfig := c.CurrentConfig() network.SetPreferIPv6(agentConfig.PreferIPv6()) // agent.Jobs is an optional field in the agent config, and was // introduced after 1.17.2. We default to allowing units on // machine-0 if missing. jobs := agentConfig.Jobs() if len(jobs) == 0 { jobs = []multiwatcher.MachineJob{ multiwatcher.JobManageModel, multiwatcher.JobHostUnits, multiwatcher.JobManageNetworking, } } // Get the bootstrap machine's addresses from the provider. env, err := environs.New(envCfg) if err != nil { return err } newConfigAttrs := make(map[string]interface{}) // Check to see if a newer agent version has been requested // by the bootstrap client. desiredVersion, ok := envCfg.AgentVersion() if ok && desiredVersion != jujuversion.Current { // If we have been asked for a newer version, ensure the newer // tools can actually be found, or else bootstrap won't complete. stream := envtools.PreferredStream(&desiredVersion, envCfg.Development(), envCfg.AgentStream()) logger.Infof("newer tools requested, looking for %v in stream %v", desiredVersion, stream) filter := tools.Filter{ Number: desiredVersion, Arch: arch.HostArch(), Series: series.HostSeries(), } _, toolsErr := envtools.FindTools(env, -1, -1, stream, filter) if toolsErr == nil { logger.Infof("tools are available, upgrade will occur after bootstrap") } if errors.IsNotFound(toolsErr) { // Newer tools not available, so revert to using the tools // matching the current agent version. logger.Warningf("newer tools for %q not available, sticking with version %q", desiredVersion, jujuversion.Current) newConfigAttrs["agent-version"] = jujuversion.Current.String() } else if toolsErr != nil { logger.Errorf("cannot find newer tools: %v", toolsErr) return toolsErr } } instanceId := instance.Id(c.InstanceId) instances, err := env.Instances([]instance.Id{instanceId}) if err != nil { return err } addrs, err := instances[0].Addresses() if err != nil { return err } // When machine addresses are reported from state, they have // duplicates removed. We should do the same here so that // there is not unnecessary churn in the mongo replicaset. // TODO (cherylj) Add explicit unit tests for this - tracked // by bug #1544158. addrs = network.MergedAddresses([]network.Address{}, addrs) // Generate a private SSH key for the controllers, and add // the public key to the environment config. We'll add the // private key to StateServingInfo below. privateKey, publicKey, err := sshGenerateKey(config.JujuSystemKey) if err != nil { return errors.Annotate(err, "failed to generate system key") } authorizedKeys := config.ConcatAuthKeys(envCfg.AuthorizedKeys(), publicKey) newConfigAttrs[config.AuthKeysConfig] = authorizedKeys // Generate a shared secret for the Mongo replica set, and write it out. sharedSecret, err := mongo.GenerateSharedSecret() if err != nil { return err } info, ok := agentConfig.StateServingInfo() if !ok { return fmt.Errorf("bootstrap machine config has no state serving info") } info.SharedSecret = sharedSecret info.SystemIdentity = privateKey err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { agentConfig.SetStateServingInfo(info) return nil }) if err != nil { return fmt.Errorf("cannot write agent config: %v", err) } err = c.ChangeConfig(func(config agent.ConfigSetter) error { // We'll try for mongo 3.2 first and fallback to // mongo 2.4 if the newer binaries are not available. if mongo.BinariesAvailable(mongo.Mongo32wt) { config.SetMongoVersion(mongo.Mongo32wt) } else { config.SetMongoVersion(mongo.Mongo24) } return nil }) if err != nil { return errors.Annotate(err, "cannot set mongo version") } agentConfig = c.CurrentConfig() // Create system-identity file if err := agent.WriteSystemIdentityFile(agentConfig); err != nil { return err } if err := c.startMongo(addrs, agentConfig); err != nil { return err } logger.Infof("started mongo") // Initialise state, and store any agent config (e.g. password) changes. envCfg, err = env.Config().Apply(newConfigAttrs) if err != nil { return errors.Annotate(err, "failed to update model config") } var st *state.State var m *state.Machine err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { var stateErr error dialOpts := mongo.DefaultDialOpts() // Set a longer socket timeout than usual, as the machine // will be starting up and disk I/O slower than usual. This // has been known to cause timeouts in queries. timeouts := envCfg.BootstrapSSHOpts() dialOpts.SocketTimeout = timeouts.Timeout if dialOpts.SocketTimeout < minSocketTimeout { dialOpts.SocketTimeout = minSocketTimeout } // We shouldn't attempt to dial peers until we have some. dialOpts.Direct = true adminTag := names.NewLocalUserTag(c.AdminUsername) st, m, stateErr = agentInitializeState( adminTag, agentConfig, envCfg, c.HostedModelConfig, agentbootstrap.BootstrapMachineConfig{ Addresses: addrs, BootstrapConstraints: c.BootstrapConstraints, ModelConstraints: c.ModelConstraints, Jobs: jobs, InstanceId: instanceId, Characteristics: c.Hardware, SharedSecret: sharedSecret, }, dialOpts, environs.NewStatePolicy(), ) return stateErr }) if err != nil { return err } defer st.Close() // Populate the tools catalogue. if err := c.populateTools(st, env); err != nil { return err } // Populate the GUI archive catalogue. if err := c.populateGUIArchive(st, env); err != nil { // Do not stop the bootstrapping process for Juju GUI archive errors. logger.Warningf("cannot set up Juju GUI: %s", err) } else { logger.Debugf("Juju GUI successfully set up") } // Add custom image metadata to environment storage. if c.ImageMetadataDir != "" { if err := c.saveCustomImageMetadata(st, env); err != nil { return err } stor := newStateStorage(st.ModelUUID(), st.MongoSession()) if err := c.storeCustomImageMetadata(stor); err != nil { return err } } // Populate the storage pools. if err = c.populateDefaultStoragePools(st); err != nil { return err } // bootstrap machine always gets the vote return m.SetHasVote(true) }
func isMigrationActive(c *gc.C, st *state.State) bool { isActive, err := state.IsModelMigrationActive(st, st.ModelUUID()) c.Assert(err, jc.ErrorIsNil) return isActive }
func (s *migrationSuite) startSync(c *gc.C, st *state.State) { backingSt, err := s.BackingStatePool.Get(st.ModelUUID()) c.Assert(err, jc.ErrorIsNil) backingSt.StartSync() }
// newBakeryService creates a new bakery.Service. func newBakeryService(st *state.State, locator bakery.PublicKeyLocator) (*bakery.Service, error) { return bakery.NewService(bakery.NewServiceParams{ Location: "juju model " + st.ModelUUID(), Locator: locator, }) }
func (s *LogsSuite) countLogs(c *gc.C, st *state.State) int { count, err := s.logsColl.Find(bson.M{"e": st.ModelUUID()}).Count() c.Assert(err, jc.ErrorIsNil) return count }