// 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.EnvironUUID(), 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 }
// 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 }
// migrateCharmStorage copies uploaded charms from provider storage // to environment storage, and then adds the storage path into the // charm's document in state. func migrateCharmStorage(st *state.State, agentConfig agent.Config) error { logger.Debugf("migrating charms to environment storage") charms, err := st.AllCharms() if err != nil { return err } storage := storage.NewStorage(st.EnvironUUID(), st.MongoSession()) // Local and manual provider host storage on the state server's // filesystem, and serve via HTTP storage. The storage worker // doesn't run yet, so we just open the files directly. fetchCharmArchive := fetchCharmArchive providerType := agentConfig.Value(agent.ProviderType) if providerType == provider.Local || provider.IsManual(providerType) { storageDir := agentConfig.Value(agent.StorageDir) fetchCharmArchive = localstorage{storageDir}.fetchCharmArchive } storagePaths := make(map[*charm.URL]string) for _, ch := range charms { if ch.IsPlaceholder() { logger.Debugf("skipping %s, placeholder charm", ch.URL()) continue } if !ch.IsUploaded() { logger.Debugf("skipping %s, not uploaded to provider storage", ch.URL()) continue } if charmStoragePath(ch) != "" { logger.Debugf("skipping %s, already in environment storage", ch.URL()) continue } url := charmBundleURL(ch) if url == nil { logger.Debugf("skipping %s, has no bundle URL", ch.URL()) continue } uuid, err := utils.NewUUID() if err != nil { return err } data, err := fetchCharmArchive(url) if err != nil { return err } curl := ch.URL() storagePath := fmt.Sprintf("charms/%s-%s", curl, uuid) logger.Debugf("uploading %s to %q in environment storage", curl, storagePath) err = storage.Put(storagePath, bytes.NewReader(data), int64(len(data))) if err != nil { return errors.Annotatef(err, "failed to upload %s to storage", curl) } storagePaths[curl] = storagePath } return stateAddCharmStoragePaths(st, storagePaths) }
func upgradeMongoWatcher(st *state.State, stopch <-chan struct{}, machineID string, maybeStopMongo StopMongo) error { m, err := st.Machine(machineID) if err != nil { return errors.Annotatef(err, "cannot start watcher for machine %q", machineID) } watch := m.Watch() defer func() { watch.Kill() watch.Wait() }() for { select { case <-watch.Changes(): if err := m.Refresh(); err != nil { return errors.Annotate(err, "cannot refresh machine information") } if !m.IsManager() { continue } expectedVersion, err := m.StopMongoUntilVersion() if err != nil { return errors.Annotate(err, "cannot obtain minimum version of mongo") } if expectedVersion == mongo.Mongo24 { continue } var isMaster bool isMaster, err = mongo.IsMaster(st.MongoSession(), m) if err != nil { return errors.Annotatef(err, "cannot determine if machine %q is master", machineID) } err = maybeStopMongo(expectedVersion, isMaster) if err != nil { return errors.Annotate(err, "cannot determine if mongo must be stopped") } if !isMaster { addrs := make([]string, len(m.Addresses())) ssi, err := st.StateServingInfo() if err != nil { return errors.Annotate(err, "cannot obtain state serving info to stop mongo") } for i, addr := range m.Addresses() { addrs[i] = net.JoinHostPort(addr.Value, strconv.Itoa(ssi.StatePort)) } if err := replicaset.Remove(st.MongoSession(), addrs...); err != nil { return errors.Annotatef(err, "cannot remove %q from replicaset", m.Id()) } if err := m.SetStopMongoUntilVersion(mongo.Mongo24); err != nil { return errors.Annotate(err, "cannot reset stop mongo flag") } } case <-stopch: return nil } } }
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 }
// migrateCustomImageMetadata copies uploaded image metadata from provider // storage to environment storage, preserving paths. func migrateCustomImageMetadata(st *state.State, agentConfig agent.Config) error { logger.Debugf("migrating custom image metadata to environment storage") estor := newStateStorage(st.EnvironUUID(), st.MongoSession()) // Local and manual provider host storage on the state server's // filesystem, and serve via HTTP storage. The storage worker // doesn't run yet, so we just open the files directly. var pstor storage.StorageReader providerType := agentConfig.Value(agent.ProviderType) if providerType == provider.Local || provider.IsManual(providerType) { storageDir := agentConfig.Value(agent.StorageDir) var err error pstor, err = filestorage.NewFileStorageReader(storageDir) if err != nil { return errors.Annotate(err, "cannot get local filesystem storage reader") } } else { var err error pstor, err = environs.LegacyStorage(st) if errors.IsNotSupported(err) { return nil } else if err != nil { return errors.Annotate(err, "cannot get provider storage") } } paths, err := pstor.List(storage.BaseImagesPath) if err != nil { return err } for _, path := range paths { logger.Infof("migrating image metadata at path %q", path) data, err := readImageMetadata(pstor, path) if err != nil { return errors.Annotate(err, "failed to read image metadata") } err = estor.Put(path, bytes.NewReader(data), int64(len(data))) if err != nil { return errors.Annotate(err, "failed to write image metadata") } } return 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 }
func isMachineMaster(st *state.State, tag names.MachineTag) (bool, error) { if st == nil { // If there is no state, we aren't a master. return false, nil } // Not calling the agent openState method as it does other checks // we really don't care about here. All we need here is the machine // so we can determine if we are the master or not. machine, err := st.Machine(tag.Id()) if err != nil { // This shouldn't happen, and if it does, the state worker will have // found out before us, and already errored, or is likely to error out // very shortly. All we do here is return the error. The state worker // returns an error that will cause the agent to be terminated. return false, errors.Trace(err) } isMaster, err := mongo.IsMaster(st.MongoSession(), machine) if err != nil { return false, errors.Trace(err) } return isMaster, nil }
func getBackupDBWrapper(st *state.State) *storageDBWrapper { envUUID := st.EnvironTag().Id() db := st.MongoSession().DB(storageDBName) return newStorageDBWrapper(db, storageMetaName, envUUID) }
// 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) }
// Run initializes state for an environment. func (c *BootstrapCommand) Run(_ *cmd.Context) error { envCfg, err := config.New(config.NoDefaults, c.EnvConfig) if err != nil { return err } err = c.ReadConfig("machine-0") if err != nil { return err } agentConfig := c.CurrentConfig() network.InitializeFromConfig(agentConfig) // 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.JobManageEnviron, multiwatcher.JobHostUnits, multiwatcher.JobManageNetworking, } } // Get the bootstrap machine's addresses from the provider. env, err := environs.New(envCfg) if err != nil { return err } 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 } // Generate a private SSH key for the state servers, 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) envCfg, err = env.Config().Apply(map[string]interface{}{ config.AuthKeysConfig: authorizedKeys, }) if err != nil { return errors.Annotate(err, "failed to add public key to environment config") } // 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) } 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. 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, agent.BootstrapMachineConfig{ Addresses: addrs, Constraints: c.Constraints, 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 } // Add custom image metadata to environment storage. if c.ImageMetadataDir != "" { if err := c.saveCustomImageMetadata(st); err != nil { return err } // TODO (anastasiamac 2015-09-24) Remove this once search path is updated.. stor := newStateStorage(st.EnvironUUID(), 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) }