func (*format_1_16Suite) TestReadConfReadsLegacyFormatAndWritesNew(c *gc.C) { dataDir := c.MkDir() formatPath := filepath.Join(dataDir, legacyFormatFilename) err := utils.AtomicWriteFile(formatPath, []byte(legacyFormatFileContents), 0600) c.Assert(err, gc.IsNil) configPath := filepath.Join(dataDir, agentConfigFilename) err = utils.AtomicWriteFile(configPath, []byte(agentConfig1_16Contents), 0600) c.Assert(err, gc.IsNil) config, err := ReadConfig(configPath) c.Assert(err, gc.IsNil) c.Assert(config, gc.NotNil) // Test we wrote a currently valid config. config, err = ReadConfig(configPath) c.Assert(err, gc.IsNil) c.Assert(config, gc.NotNil) c.Assert(config.UpgradedToVersion(), jc.DeepEquals, version.MustParse("1.16.0")) c.Assert(config.Jobs(), gc.HasLen, 0) // Old format was deleted. assertFileNotExist(c, formatPath) // And new contents were written. data, err := ioutil.ReadFile(configPath) c.Assert(err, gc.IsNil) c.Assert(string(data), gc.Not(gc.Equals), agentConfig1_16Contents) }
func (s *format_1_16Suite) TestMissingAttributes(c *gc.C) { logDir, err := paths.LogDir(series.HostSeries()) c.Assert(err, jc.ErrorIsNil) realDataDir, err := paths.DataDir(series.HostSeries()) c.Assert(err, jc.ErrorIsNil) realDataDir = filepath.FromSlash(realDataDir) logPath := filepath.Join(logDir, "juju") logPath = filepath.FromSlash(logPath) dataDir := c.MkDir() formatPath := filepath.Join(dataDir, legacyFormatFilename) err = utils.AtomicWriteFile(formatPath, []byte(legacyFormatFileContents), 0600) c.Assert(err, jc.ErrorIsNil) configPath := filepath.Join(dataDir, agentConfigFilename) err = utils.AtomicWriteFile(configPath, []byte(configDataWithoutNewAttributes), 0600) c.Assert(err, jc.ErrorIsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, jc.ErrorIsNil) c.Assert(readConfig.UpgradedToVersion(), gc.Equals, version.MustParse("1.16.0")) configLogDir := filepath.FromSlash(readConfig.LogDir()) configDataDir := filepath.FromSlash(readConfig.DataDir()) c.Assert(configLogDir, gc.Equals, logPath) c.Assert(configDataDir, gc.Equals, realDataDir) // Test data doesn't include a StateServerKey so StateServingInfo // should *not* be available _, available := readConfig.StateServingInfo() c.Assert(available, jc.IsFalse) }
// EnsureMongoServer ensures that the correct mongo upstart script is installed // and running. // // This method will remove old versions of the mongo upstart script as necessary // before installing the new version. // // The namespace is a unique identifier to prevent multiple instances of mongo // on this machine from colliding. This should be empty unless using // the local provider. func EnsureServer(dataDir string, namespace string, info params.StateServingInfo) error { logger.Infof("Ensuring mongo server is running; data directory %s; port %d", dataDir, info.StatePort) dbDir := filepath.Join(dataDir, "db") if err := os.MkdirAll(dbDir, 0700); err != nil { return fmt.Errorf("cannot create mongo database directory: %v", err) } certKey := info.Cert + "\n" + info.PrivateKey err := utils.AtomicWriteFile(sslKeyPath(dataDir), []byte(certKey), 0600) if err != nil { return fmt.Errorf("cannot write SSL key: %v", err) } err = utils.AtomicWriteFile(sharedSecretPath(dataDir), []byte(info.SharedSecret), 0600) if err != nil { return fmt.Errorf("cannot write mongod shared secret: %v", err) } // Disable the default mongodb installed by the mongodb-server package. // Only do this if the file doesn't exist already, so users can run // their own mongodb server if they wish to. if _, err := os.Stat(mongoConfigPath); os.IsNotExist(err) { err = utils.AtomicWriteFile( mongoConfigPath, []byte("ENABLE_MONGODB=no"), 0644, ) if err != nil { return err } } if err := aptGetInstallMongod(); err != nil { return fmt.Errorf("cannot install mongod: %v", err) } upstartConf, mongoPath, err := upstartService(namespace, dataDir, dbDir, info.StatePort) if err != nil { return err } logVersion(mongoPath) if err := upstartServiceStop(&upstartConf.Service); err != nil { return fmt.Errorf("failed to stop mongo: %v", err) } if err := makeJournalDirs(dbDir); err != nil { return fmt.Errorf("error creating journal directories: %v", err) } if err := preallocOplog(dbDir); err != nil { return fmt.Errorf("error creating oplog files: %v", err) } return upstartConfInstall(upstartConf) }
func (*format_1_16Suite) TestStatePortParsed(c *gc.C) { dataDir := c.MkDir() formatPath := filepath.Join(dataDir, legacyFormatFilename) err := utils.AtomicWriteFile(formatPath, []byte(legacyFormatFileContents), 0600) c.Assert(err, gc.IsNil) configPath := filepath.Join(dataDir, agentConfigFilename) err = utils.AtomicWriteFile(configPath, []byte(stateMachineConfigData), 0600) c.Assert(err, gc.IsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, gc.IsNil) info, available := readConfig.StateServingInfo() c.Assert(available, gc.Equals, true) c.Assert(info.StatePort, gc.Equals, 37017) }
// WriteCredentialsFile marshals to YAML details of the given credentials // and writes it to the credentials file. func WriteCredentialsFile(credentials map[string]cloud.CloudCredential) error { data, err := yaml.Marshal(credentialsCollection{credentials}) if err != nil { return errors.Annotate(err, "cannot marshal yaml credentials") } return utils.AtomicWriteFile(JujuCredentialsPath(), data, os.FileMode(0600)) }
// writeOrRemove writes the new content of the file or removes it according to Op field. func (cf ConfigFiles) writeOrRemove() error { // Create /etc/network/interfaces.d directory if absent. if _, err := os.Stat(configSubDirName); err != nil { err := os.Mkdir(configSubDirName, 0755) if err != nil { logger.Errorf("failed to create directory %q: %v", configSubDirName, err) return err } } for fileName, f := range cf { if f.Op == doRemove { err := os.Remove(fileName) if err != nil { logger.Errorf("failed to remove file %q: %v", fileName, err) return err } } else if f.Op == doWrite { err := utils.AtomicWriteFile(fileName, []byte(f.Data), 0644) if err != nil { logger.Errorf("failed to white file %q: %v", fileName, err) return err } } } return nil }
// WriteControllersFile marshals to YAML details of the given controllers // and writes it to the controllers file. func WriteControllersFile(controllers *Controllers) error { data, err := yaml.Marshal(controllers) if err != nil { return errors.Annotate(err, "cannot marshal yaml controllers") } return utils.AtomicWriteFile(JujuControllersPath(), data, os.FileMode(0600)) }
// WriteControllersFile marshals to YAML details of the given controllers // and writes it to the controllers file. func WriteControllersFile(controllers map[string]ControllerDetails) error { data, err := yaml.Marshal(controllersCollection{controllers}) if err != nil { return errors.Annotate(err, "cannot marshal yaml controllers") } return utils.AtomicWriteFile(JujuControllersPath(), data, os.FileMode(0600)) }
// WriteBootstrapConfigFile marshals to YAML details of the given bootstrap // configurations and writes it to the bootstrap config file. func WriteBootstrapConfigFile(configs map[string]BootstrapConfig) error { data, err := yaml.Marshal(bootstrapConfigCollection{configs}) if err != nil { return errors.Annotate(err, "cannot marshal bootstrap configurations") } return utils.AtomicWriteFile(JujuBootstrapConfigPath(), data, os.FileMode(0600)) }
// Apply implements ConfigFile.Apply(). func (f *configFile) Apply() error { if f.needsUpdating { err := utils.AtomicWriteFile(f.fileName, f.data, 0644) if err != nil { logger.Errorf("failed to write file %q: %v", f.fileName, err) return err } if f.interfaceName == "" { logger.Debugf("updated main network config %q", f.fileName) } else { logger.Debugf("updated network config %q for %q", f.fileName, f.interfaceName) } f.needsUpdating = false } if f.pendingRemoval { err := os.Remove(f.fileName) if err != nil { logger.Errorf("failed to remove file %q: %v", f.fileName, err) return err } logger.Debugf("removed config %q for %q", f.fileName, f.interfaceName) f.pendingRemoval = false } return nil }
// WriteAccountsFile marshals to YAML details of the given accounts // and writes it to the accounts file. func WriteAccountsFile(controllerAccounts map[string]AccountDetails) error { data, err := yaml.Marshal(accountsCollection{controllerAccounts}) if err != nil { return errors.Annotate(err, "cannot marshal accounts") } return utils.AtomicWriteFile(JujuAccountsPath(), data, os.FileMode(0600)) }
func (s *format_1_18Suite) TestMissingAttributes(c *gc.C) { logDir, err := paths.LogDir(series.HostSeries()) c.Assert(err, jc.ErrorIsNil) realDataDir, err := paths.DataDir(series.HostSeries()) c.Assert(err, jc.ErrorIsNil) realDataDir = filepath.FromSlash(realDataDir) logPath := filepath.Join(logDir, "juju") logPath = filepath.FromSlash(logPath) dataDir := c.MkDir() configPath := filepath.Join(dataDir, agentConfigFilename) err = utils.AtomicWriteFile(configPath, []byte(configData1_18WithoutUpgradedToVersion), 0600) c.Assert(err, jc.ErrorIsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, jc.ErrorIsNil) c.Assert(readConfig.UpgradedToVersion(), gc.Equals, version.MustParse("1.16.0")) configLogDir := filepath.FromSlash(readConfig.LogDir()) configDataDir := filepath.FromSlash(readConfig.DataDir()) c.Assert(configLogDir, gc.Equals, logPath) c.Assert(configDataDir, gc.Equals, realDataDir) c.Assert(readConfig.PreferIPv6(), jc.IsFalse) // The api info doesn't have the environment tag set. apiInfo, ok := readConfig.APIInfo() c.Assert(ok, jc.IsTrue) c.Assert(apiInfo.EnvironTag.Id(), gc.Equals, "") }
// WriteModelsFile marshals to YAML details of the given models // and writes it to the models file. func WriteModelsFile(models map[string]ControllerAccountModels) error { data, err := yaml.Marshal(modelsCollection{models}) if err != nil { return errors.Annotate(err, "cannot marshal models") } return utils.AtomicWriteFile(JujuModelsPath(), data, os.FileMode(0600)) }
// WritePublicCloudMetadata marshals to YAML and writes the cloud metadata // to the public cloud file. func WritePublicCloudMetadata(cloudsMap map[string]Cloud) error { data, err := marshalCloudMetadata(cloudsMap) if err != nil { return errors.Trace(err) } return utils.AtomicWriteFile(JujuPublicCloudsPath(), data, 0600) }
func (s *format_1_16Suite) TestMissingAttributes(c *gc.C) { dataDir := c.MkDir() formatPath := filepath.Join(dataDir, legacyFormatFilename) err := utils.AtomicWriteFile(formatPath, []byte(legacyFormatFileContents), 0600) c.Assert(err, gc.IsNil) configPath := filepath.Join(dataDir, agentConfigFilename) err = utils.AtomicWriteFile(configPath, []byte(configDataWithoutNewAttributes), 0600) c.Assert(err, gc.IsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, gc.IsNil) c.Assert(readConfig.UpgradedToVersion(), gc.Equals, version.MustParse("1.16.0")) c.Assert(readConfig.LogDir(), gc.Equals, "/var/log/juju") c.Assert(readConfig.DataDir(), gc.Equals, "/var/lib/juju") // Test data doesn't include a StateServerKey so StateServingInfo // should *not* be available _, available := readConfig.StateServingInfo() c.Assert(available, gc.Equals, false) }
func writeAuthorisedKeys(username string, keys []string) error { keyDir := fmt.Sprintf(authKeysDir, username) keyDir, err := utils.NormalizePath(keyDir) if err != nil { return err } err = os.MkdirAll(keyDir, os.FileMode(0755)) if err != nil { return fmt.Errorf("cannot create ssh key directory: %v", err) } keyData := strings.Join(keys, "\n") + "\n" // Get perms to use on auth keys file sshKeyFile := filepath.Join(keyDir, authKeysFile) perms := os.FileMode(0644) info, err := os.Stat(sshKeyFile) if err == nil { perms = info.Mode().Perm() } logger.Debugf("writing authorised keys file %s", sshKeyFile) err = utils.AtomicWriteFile(sshKeyFile, []byte(keyData), perms) if err != nil { return err } // TODO (wallyworld) - what to do on windows (if anything) // TODO(dimitern) - no need to use user.Current() if username // is "" - it will use the current user anyway. if runtime.GOOS != "windows" { // Ensure the resulting authorised keys file has its ownership // set to the specified username. var u *user.User if username == "" { u, err = user.Current() } else { u, err = user.Lookup(username) } if err != nil { return err } // chown requires ints but user.User has strings for windows. uid, err := strconv.Atoi(u.Uid) if err != nil { return err } gid, err := strconv.Atoi(u.Gid) if err != nil { return err } err = os.Chown(sshKeyFile, uid, gid) if err != nil { return err } } return nil }
func (s *format_1_18Suite) TestStatePortNotParsedWithoutSecret(c *gc.C) { dataDir := c.MkDir() configPath := filepath.Join(dataDir, agentConfigFilename) err := utils.AtomicWriteFile(configPath, []byte(agentConfig1_18NotStateMachine), 0600) c.Assert(err, jc.ErrorIsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, jc.ErrorIsNil) _, available := readConfig.StateServingInfo() c.Assert(available, jc.IsFalse) }
func (*format_2_0Suite) TestReadConfWithExisting2_0ConfigFileContents(c *gc.C) { dataDir := c.MkDir() configPath := filepath.Join(dataDir, agentConfigFilename) err := utils.AtomicWriteFile(configPath, []byte(agentConfig2_0Contents), 0600) c.Assert(err, jc.ErrorIsNil) config, err := ReadConfig(configPath) c.Assert(err, jc.ErrorIsNil) c.Assert(config.UpgradedToVersion(), jc.DeepEquals, version.MustParse("1.17.5.1")) c.Assert(config.Jobs(), jc.DeepEquals, []multiwatcher.MachineJob{multiwatcher.JobManageModel}) }
func (*format_1_18Suite) TestReadConfWithExisting1_18ConfigFileContents(c *gc.C) { dataDir := c.MkDir() configPath := filepath.Join(dataDir, agentConfigFilename) err := utils.AtomicWriteFile(configPath, []byte(agentConfig1_18Contents), 0600) c.Assert(err, gc.IsNil) config, err := ReadConfig(configPath) c.Assert(err, gc.IsNil) c.Assert(config.UpgradedToVersion(), jc.DeepEquals, version.MustParse("1.17.5.1")) c.Assert(config.Jobs(), jc.DeepEquals, []params.MachineJob{params.JobManageEnviron}) }
func (s *format_1_18Suite) TestMissingAttributes(c *gc.C) { dataDir := c.MkDir() configPath := filepath.Join(dataDir, agentConfigFilename) err := utils.AtomicWriteFile(configPath, []byte(configData1_18WithoutUpgradedToVersion), 0600) c.Assert(err, gc.IsNil) readConfig, err := ReadConfig(configPath) c.Assert(err, gc.IsNil) c.Assert(readConfig.UpgradedToVersion(), gc.Equals, version.MustParse("1.16.0")) c.Assert(readConfig.LogDir(), gc.Equals, "/var/log/juju") c.Assert(readConfig.DataDir(), gc.Equals, "/var/lib/juju") }
func (c *configInternal) Write() error { data, err := c.fileContents() if err != nil { return err } // Make sure the config dir gets created. configDir := filepath.Dir(c.configFilePath) if err := os.MkdirAll(configDir, 0755); err != nil { return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err) } return utils.AtomicWriteFile(c.configFilePath, data, 0600) }
// maybeOverrideDefaultLXCNet writes a modified version of // /etc/default/lxc-net file on the host before installing the lxc // package, if we're about to start an addressable LXC container. This // is needed to guarantee stable statically assigned IP addresses for // the container. See also runInitialiser. func maybeOverrideDefaultLXCNet(containerType instance.ContainerType, addressable bool) error { if containerType != instance.LXC || !addressable { // Nothing to do. return nil } err := utils.AtomicWriteFile(etcDefaultLXCNetPath, []byte(etcDefaultLXCNet), 0644) if err != nil { return errors.Annotatef(err, "cannot write %q", etcDefaultLXCNetPath) } return nil }
func (s *CharmSuite) TestUpdateUploadedCharmEscapesSpecialCharsInConfig(c *gc.C) { // Make sure when we have mongodb special characters like "$" and // "." in the name of any charm config option, we do proper // escaping before storing them and unescaping after loading. See // also http://pad.lv/1308146. // Clone the dummy charm and change the config. configWithProblematicKeys := []byte(` options: $bad.key: {default: bad, description: bad, type: string} not.ok.key: {description: not ok, type: int} valid-key: {description: all good, type: boolean} still$bad.: {description: not good, type: float} $.$: {description: awful, type: string} ...: {description: oh boy, type: int} just$: {description: no no, type: float} `[1:]) chDir := testcharms.Repo.ClonedDirPath(c.MkDir(), "dummy") err := utils.AtomicWriteFile( filepath.Join(chDir, "config.yaml"), configWithProblematicKeys, 0666, ) c.Assert(err, jc.ErrorIsNil) ch, err := charm.ReadCharmDir(chDir) c.Assert(err, jc.ErrorIsNil) missingCurl := charm.MustParseURL("local:quantal/missing-1") storagePath := "dummy-1" preparedCurl, err := s.State.PrepareLocalCharmUpload(missingCurl) c.Assert(err, jc.ErrorIsNil) info := state.CharmInfo{ Charm: ch, ID: preparedCurl, StoragePath: "dummy-1", SHA256: "missing", } sch, err := s.State.UpdateUploadedCharm(info) c.Assert(err, jc.ErrorIsNil) c.Assert(sch.URL(), gc.DeepEquals, missingCurl) c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision) c.Assert(sch.IsUploaded(), jc.IsTrue) c.Assert(sch.IsPlaceholder(), jc.IsFalse) c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta()) c.Assert(sch.Config(), gc.DeepEquals, ch.Config()) c.Assert(sch.StoragePath(), gc.DeepEquals, storagePath) c.Assert(sch.BundleSha256(), gc.Equals, "missing") }
func WriteSystemIdentityFile(c Config) error { info, ok := c.StateServingInfo() if !ok { return fmt.Errorf("StateServingInfo not available and we need it") } // Write non-empty contents to the file, otherwise delete it if info.SystemIdentity != "" { err := utils.AtomicWriteFile(c.SystemIdentityPath(), []byte(info.SystemIdentity), 0600) if err != nil { return fmt.Errorf("cannot write system identity: %v", err) } } else { os.Remove(c.SystemIdentityPath()) } return nil }
func WriteSystemIdentityFile(c Config) error { info, ok := c.StateServingInfo() if !ok { return errors.Trace(ErrNoStateServingInfo) } // Write non-empty contents to the file, otherwise delete it if info.SystemIdentity != "" { logger.Infof("writing system identity file") err := utils.AtomicWriteFile(c.SystemIdentityPath(), []byte(info.SystemIdentity), 0600) if err != nil { return errors.Annotate(err, "cannot write system identity") } } else { logger.Infof("removing system identity file") os.Remove(c.SystemIdentityPath()) } return nil }
// UpdateSSLKey writes a new SSL key used by mongo to validate connections from Juju controller(s) func UpdateSSLKey(dataDir, cert, privateKey string) error { certKey := cert + "\n" + privateKey err := utils.AtomicWriteFile(sslKeyPath(dataDir), []byte(certKey), 0600) return errors.Annotate(err, "cannot write SSL key") }
// EnsureServer ensures that the MongoDB server is installed, // configured, and ready to run. // // This method will remove old versions of the mongo init service as necessary // before installing the new version. func EnsureServer(args EnsureServerParams) error { logger.Infof( "Ensuring mongo server is running; data directory %s; port %d", args.DataDir, args.StatePort, ) dbDir := filepath.Join(args.DataDir, "db") if err := os.MkdirAll(dbDir, 0700); err != nil { return fmt.Errorf("cannot create mongo database directory: %v", err) } oplogSizeMB := args.OplogSize if oplogSizeMB == 0 { var err error if oplogSizeMB, err = defaultOplogSize(dbDir); err != nil { return err } } operatingsystem := series.HostSeries() if err := installMongod(operatingsystem, args.SetNumaControlPolicy); err != nil { // This isn't treated as fatal because the Juju MongoDB // package is likely to be already installed anyway. There // could just be a temporary issue with apt-get/yum/whatever // and we don't want this to stop jujud from starting. // (LP #1441904) logger.Errorf("cannot install/upgrade mongod (will proceed anyway): %v", err) } mongoPath, err := Path() if err != nil { return err } logVersion(mongoPath) if err := UpdateSSLKey(args.DataDir, args.Cert, args.PrivateKey); err != nil { return err } err = utils.AtomicWriteFile(sharedSecretPath(args.DataDir), []byte(args.SharedSecret), 0600) if err != nil { return fmt.Errorf("cannot write mongod shared secret: %v", err) } // Disable the default mongodb installed by the mongodb-server package. // Only do this if the file doesn't exist already, so users can run // their own mongodb server if they wish to. if _, err := os.Stat(mongoConfigPath); os.IsNotExist(err) { err = utils.AtomicWriteFile( mongoConfigPath, []byte("ENABLE_MONGODB=no"), 0644, ) if err != nil { return err } } svcConf := newConf(args.DataDir, dbDir, mongoPath, args.StatePort, oplogSizeMB, args.SetNumaControlPolicy) svc, err := newService(ServiceName, svcConf) if err != nil { return err } installed, err := svc.Installed() if err != nil { return errors.Trace(err) } if installed { exists, err := svc.Exists() if err != nil { return errors.Trace(err) } if exists { logger.Debugf("mongo exists as expected") running, err := svc.Running() if err != nil { return errors.Trace(err) } if !running { return svc.Start() } return nil } } if err := svc.Stop(); err != nil { return errors.Annotatef(err, "failed to stop mongo") } if err := makeJournalDirs(dbDir); err != nil { return fmt.Errorf("error creating journal directories: %v", err) } if err := preallocOplog(dbDir, oplogSizeMB); err != nil { return fmt.Errorf("error creating oplog files: %v", err) } if err := service.InstallAndStart(svc); err != nil { return errors.Trace(err) } return nil }
func (s *networkerSuite) TestNetworker(c *gc.C) { // Create a sample interfaces file (MAAS configuration) interfacesFileContents := fmt.Sprintf(sampleInterfacesFile, networker.ConfigDirName) err := utils.AtomicWriteFile(networker.ConfigFileName, []byte(interfacesFileContents), 0644) c.Assert(err, gc.IsNil) err = utils.AtomicWriteFile(filepath.Join(networker.ConfigDirName, "eth0.config"), []byte(sampleEth0DotConfigFile), 0644) c.Assert(err, gc.IsNil) // Patch the network interface functions s.PatchValue(&networker.InterfaceIsUp, func(name string) bool { return readyInterfaces.Contains(name) }) s.PatchValue(&networker.InterfaceHasAddress, func(name string) bool { return interfacesWithAddress.Contains(name) }) // Patch the command executor function s.configStates = []*configState{} s.PatchValue(&networker.ExecuteCommands, func(commands []string) error { return executeCommandsHook(c, s, commands) }, ) // Create and setup networker. s.executed = make(chan bool) nw, err := networker.NewNetworker(s.networkerState, agentConfig(s.machine.Tag())) c.Assert(err, gc.IsNil) defer func() { c.Assert(worker.Stop(nw), gc.IsNil) }() executeCount := 0 loop: for { select { case <-s.executed: executeCount++ if executeCount == 3 { break loop } case <-time.After(coretesting.ShortWait): fmt.Printf("%#v\n", s.configStates) c.Fatalf("command not executed") } } // Verify the executed commands from SetUp() expectedConfigFiles := networker.ConfigFiles{ networker.ConfigFileName: { Data: fmt.Sprintf(expectedInterfacesFile, networker.ConfigSubDirName, networker.ConfigSubDirName, networker.ConfigSubDirName, networker.ConfigSubDirName), }, networker.IfaceConfigFileName("br0"): { Data: "auto br0\niface br0 inet dhcp\n bridge_ports eth0\n", }, networker.IfaceConfigFileName("eth0"): { Data: "auto eth0\niface eth0 inet manual\n", }, networker.IfaceConfigFileName("wlan0"): { Data: "auto wlan0\niface wlan0 inet dhcp\n", }, } c.Assert(s.configStates[0].files, gc.DeepEquals, expectedConfigFiles) expectedCommands := []string(nil) c.Assert(s.configStates[0].commands, gc.DeepEquals, expectedCommands) c.Assert(s.configStates[0].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"}) c.Assert(s.configStates[0].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"}) // Verify the executed commands from Handle() c.Assert(s.configStates[1].files, gc.DeepEquals, expectedConfigFiles) expectedCommands = []string(nil) c.Assert(s.configStates[1].commands, gc.DeepEquals, expectedCommands) c.Assert(s.configStates[1].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"}) c.Assert(s.configStates[1].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"}) // Verify the executed commands from Handle() expectedConfigFiles[networker.IfaceConfigFileName("eth0.69")] = &networker.ConfigFile{ Data: "# Managed by Juju, don't change.\nauto eth0.69\niface eth0.69 inet dhcp\n\tvlan-raw-device eth0\n", } expectedConfigFiles[networker.IfaceConfigFileName("eth1")] = &networker.ConfigFile{ Data: "# Managed by Juju, don't change.\nauto eth1\niface eth1 inet dhcp\n", } expectedConfigFiles[networker.IfaceConfigFileName("eth1.42")] = &networker.ConfigFile{ Data: "# Managed by Juju, don't change.\nauto eth1.42\niface eth1.42 inet dhcp\n\tvlan-raw-device eth1\n", } expectedConfigFiles[networker.IfaceConfigFileName("eth2")] = &networker.ConfigFile{ Data: "# Managed by Juju, don't change.\nauto eth2\niface eth2 inet dhcp\n", } for k, _ := range s.configStates[2].files { c.Check(s.configStates[2].files[k], gc.DeepEquals, expectedConfigFiles[k]) } c.Assert(s.configStates[2].files, gc.DeepEquals, expectedConfigFiles) expectedCommands = []string{ "dpkg-query -s vlan || apt-get --option Dpkg::Options::=--force-confold --assume-yes install vlan", "lsmod | grep -q 8021q || modprobe 8021q", "grep -q 8021q /etc/modules || echo 8021q >> /etc/modules", "vconfig set_name_type DEV_PLUS_VID_NO_PAD", "ifup eth0.69", "ifup eth1", "ifup eth1.42", "ifup eth2", } c.Assert(s.configStates[2].commands, gc.DeepEquals, expectedCommands) c.Assert(s.configStates[2].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"}) c.Assert(s.configStates[2].interfacesWithAddress, gc.DeepEquals, []string{"br0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"}) }
// updateContainerConfig selectively replaces, deletes, and/or appends // lines in the named container's current config file, depending on // the contents of newConfig. First, newConfig is split into multiple // lines and parsed, ignoring comments, empty lines and spaces. Then // the occurrence of a setting in a line of the config file will be // replaced by values from newConfig. Values in newConfig are only // used once (in the order provided), so multiple replacements must be // supplied as multiple input values for the same setting in // newConfig. If the value of a setting is empty, the setting will be // removed if found. Settings that are not found and have values will // be appended (also if more values are given than exist). // // For example, with existing config like "lxc.foo = off\nlxc.bar=42\n", // and newConfig like "lxc.bar=\nlxc.foo = bar\nlxc.foo = baz # xx", // the updated config file contains "lxc.foo = bar\nlxc.foo = baz\n". // TestUpdateContainerConfig has this example in code. func updateContainerConfig(name, newConfig string) error { lines := strings.Split(newConfig, "\n") if len(lines) == 0 { return nil } // Extract unique set of line prefixes to match later. Also, keep // a slice of values to replace for each key, as well as a slice // of parsed prefixes to preserve the order when replacing or // appending. parsedLines := make(map[string][]string) var parsedPrefixes []string for _, line := range lines { prefix, value := parseConfigLine(line) if prefix == "" { // Ignore comments, empty lines, and unknown prefixes. continue } if values, found := parsedLines[prefix]; !found { parsedLines[prefix] = []string{value} } else { values = append(values, value) parsedLines[prefix] = values } parsedPrefixes = append(parsedPrefixes, prefix) } path := containerConfigFilename(name) currentConfig, err := ioutil.ReadFile(path) if err != nil { return errors.Annotatef(err, "cannot open config %q for container %q", path, name) } input := bytes.NewBuffer(currentConfig) // Read the original config and prepare the output to replace it // with. var output bytes.Buffer scanner := bufio.NewScanner(input) for scanner.Scan() { line := scanner.Text() prefix, _ := parseConfigLine(line) values, found := parsedLines[prefix] if !found || len(values) == 0 { // No need to change, just preserve. output.WriteString(line + "\n") continue } // We need to change this line. Pop the first value of the // list and set it. var newValue string newValue, values = values[0], values[1:] parsedLines[prefix] = values if newValue == "" { logger.Tracef("removing %q from container %q config %q", line, name, path) continue } newLine := prefix + " = " + newValue if newLine == line { // No need to change and pollute the log, just write it. output.WriteString(line + "\n") continue } logger.Tracef( "replacing %q with %q in container %q config %q", line, newLine, name, path, ) output.WriteString(newLine + "\n") } if err := scanner.Err(); err != nil { return errors.Annotatef(err, "cannot read config for container %q", name) } // Now process any prefixes with values still left for appending, // in the original order. for _, prefix := range parsedPrefixes { values := parsedLines[prefix] for _, value := range values { if value == "" { // No need to remove what's not there. continue } newLine := prefix + " = " + value + "\n" logger.Tracef("appending %q to container %q config %q", newLine, name, path) output.WriteString(newLine) } // Reset the values, so we only append the once per prefix. parsedLines[prefix] = []string{} } // Reset the original file and overwrite it atomically. if err := utils.AtomicWriteFile(path, output.Bytes(), 0644); err != nil { return errors.Annotatef(err, "cannot write new config %q for container %q", path, name) } return nil }
// reorderNetworkConfig reads the contents of the given container // config file and the modifies the contents, if needed, so that any // lxc.network.* setting comes after the first lxc.network.type // setting preserving the order. Every line formatting is preserved in // the modified config, including whitespace and comments. The // wasReordered flag will be set if the config was modified. // // This ensures the lxc tools won't report parsing errors for network // settings. See also LP bug #1414016. func reorderNetworkConfig(configFile string) (wasReordered bool, err error) { data, err := ioutil.ReadFile(configFile) if err != nil { return false, errors.Annotatef(err, "cannot read config %q", configFile) } if len(data) == 0 { // Nothing to do. return false, nil } input := bytes.NewBuffer(data) scanner := bufio.NewScanner(input) var output bytes.Buffer firstNetworkType := "" var networkLines []string mayNeedReordering := true foundFirstType := false doneReordering := false for scanner.Scan() { line := scanner.Text() prefix, _ := parseConfigLine(line) if mayNeedReordering { if strings.HasPrefix(prefix, "lxc.network.type") { if len(networkLines) == 0 { // All good, no need to change. logger.Tracef("correct network settings order in config %q", configFile) return false, nil } // We found the first type. firstNetworkType = line foundFirstType = true logger.Tracef( "moving line(s) %q after line %q in config %q", strings.Join(networkLines, "\n"), firstNetworkType, configFile, ) } else if strings.HasPrefix(prefix, "lxc.network.") { if firstNetworkType != "" { // All good, no need to change. logger.Tracef("correct network settings order in config %q", configFile) return false, nil } networkLines = append(networkLines, line) logger.Tracef("need to move line %q in config %q", line, configFile) continue } } output.WriteString(line + "\n") if foundFirstType && len(networkLines) > 0 { // Now add the skipped networkLines. output.WriteString(strings.Join(networkLines, "\n") + "\n") doneReordering = true mayNeedReordering = false firstNetworkType = "" networkLines = nil } } if err := scanner.Err(); err != nil { return false, errors.Annotatef(err, "cannot read config %q", configFile) } if !doneReordering { if len(networkLines) > 0 { logger.Errorf("invalid lxc network settings in config %q", configFile) return false, errors.Errorf( "cannot have line(s) %q without lxc.network.type in config %q", strings.Join(networkLines, "\n"), configFile, ) } // No networking settings to reorder. return false, nil } // Reset the original file and overwrite it atomically. if err := utils.AtomicWriteFile(configFile, output.Bytes(), 0644); err != nil { return false, errors.Annotatef(err, "cannot write new config %q", configFile) } logger.Tracef("reordered network settings in config %q", configFile) return true, nil }