Merge pull request #96 from philips/locksmith-support
Add locksmith support v2
This commit is contained in:
		| @@ -70,6 +70,23 @@ Note that hyphens in the coreos.etcd.* keys are mapped to underscores. | ||||
|  | ||||
| [etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md | ||||
|  | ||||
| #### update | ||||
|  | ||||
| The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated. | ||||
|  | ||||
| - **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed. | ||||
|   - _reboot_: Reboot immediately after an update is applied. | ||||
|   - _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update. | ||||
|   - _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot". | ||||
|   - _off_ - Disable rebooting after updates are applied (not recommended). | ||||
|  | ||||
| ``` | ||||
| #cloud-config | ||||
| coreos: | ||||
|   update: | ||||
|     reboot-strategy: etcd-lock | ||||
| ``` | ||||
|  | ||||
| #### oem | ||||
|  | ||||
| The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine: | ||||
|   | ||||
| @@ -13,9 +13,10 @@ import ( | ||||
| type CloudConfig struct { | ||||
| 	SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` | ||||
| 	Coreos            struct { | ||||
| 		Etcd  EtcdEnvironment | ||||
| 		Units []system.Unit | ||||
| 		OEM   OEMRelease | ||||
| 		Etcd   EtcdEnvironment | ||||
| 		Update map[string]string | ||||
| 		Units  []system.Unit | ||||
| 		OEM    OEMRelease | ||||
| 	} | ||||
| 	WriteFiles     []system.File `yaml:"write_files"` | ||||
| 	Hostname       string | ||||
| @@ -128,6 +129,13 @@ func Apply(cfg CloudConfig, env *Environment) error { | ||||
| 		log.Printf("Wrote etcd config file to filesystem") | ||||
| 	} | ||||
|  | ||||
| 	if s, ok := cfg.Coreos.Update["reboot-strategy"]; ok { | ||||
| 		if err := WriteLocksmithConfig(s, env.Root()); err != nil { | ||||
| 			log.Fatalf("Failed to write locksmith config to filesystem: %v", err) | ||||
| 		} | ||||
| 		log.Printf("Wrote locksmith config file to filesystem") | ||||
| 	} | ||||
|  | ||||
| 	if len(cfg.Coreos.Units) > 0 { | ||||
| 		commands := make(map[string]string, 0) | ||||
| 		for _, unit := range cfg.Coreos.Units { | ||||
|   | ||||
| @@ -32,6 +32,8 @@ func TestCloudConfig(t *testing.T) { | ||||
| coreos:  | ||||
|   etcd: | ||||
|     discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" | ||||
|   update: | ||||
|     reboot-strategy: reboot | ||||
|   units: | ||||
|     - name: 50-eth0.network | ||||
|       runtime: yes | ||||
| @@ -129,6 +131,9 @@ Address=10.209.171.177/19 | ||||
| 	if cfg.Hostname != "trontastic" { | ||||
| 		t.Errorf("Failed to parse hostname") | ||||
| 	} | ||||
| 	if cfg.Coreos.Update["reboot-strategy"] != "reboot" { | ||||
| 		t.Errorf("Failed to parse locksmith strategy") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Assert that our interface conversion doesn't panic | ||||
|   | ||||
							
								
								
									
										82
									
								
								initialize/locksmith.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								initialize/locksmith.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| package initialize | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
|  | ||||
| const locksmithUnit = "locksmithd.service" | ||||
|  | ||||
| // addStrategy creates an `/etc/coreos/update.conf` file with the requested | ||||
| // strategy via rewriting the file on disk or by starting from | ||||
| // `/usr/share/coreos/update.conf`. | ||||
| func addStrategy(strategy string, root string) error { | ||||
| 	etcUpdate := path.Join(root, "etc", "coreos", "update.conf") | ||||
| 	usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf") | ||||
|  | ||||
| 	tmp, err := ioutil.TempFile(path.Join(root, "etc", "coreos"), ".update.conf") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = tmp.Chmod(0644) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	conf, err := os.Open(etcUpdate) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		conf, err = os.Open(usrUpdate) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	scanner := bufio.NewScanner(conf) | ||||
|  | ||||
| 	sawStrat := false | ||||
| 	stratLine := "REBOOT_STRATEGY="+strategy | ||||
| 	for scanner.Scan() { | ||||
| 		line := scanner.Text() | ||||
| 		if strings.HasPrefix(line, "REBOOT_STRATEGY=") { | ||||
| 			line = stratLine | ||||
| 			sawStrat = true | ||||
| 		} | ||||
| 		fmt.Fprintln(tmp, line) | ||||
| 		if err := scanner.Err(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !sawStrat { | ||||
| 		fmt.Fprintln(tmp, stratLine) | ||||
| 	} | ||||
|  | ||||
| 	return os.Rename(tmp.Name(), etcUpdate) | ||||
| } | ||||
|  | ||||
| // WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith. | ||||
| func WriteLocksmithConfig(strategy string, root string) error { | ||||
| 	cmd := "restart" | ||||
| 	if strategy == "off" { | ||||
| 		err := system.MaskUnit(locksmithUnit, root) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		cmd = "stop" | ||||
| 	} else { | ||||
| 		return addStrategy(strategy, root) | ||||
| 	} | ||||
| 	if err := system.DaemonReload(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										92
									
								
								initialize/locksmith_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								initialize/locksmith_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| package initialize | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	base = `SERVER=https://example.com | ||||
| GROUP=thegroupc` | ||||
|  | ||||
| 	configured = base + ` | ||||
| REBOOT_STRATEGY=awesome | ||||
| ` | ||||
|  | ||||
| 	expected = base + ` | ||||
| REBOOT_STRATEGY=etcd-lock | ||||
| ` | ||||
| ) | ||||
|  | ||||
| func setupFixtures(dir string) { | ||||
| 	os.MkdirAll(path.Join(dir, "usr", "share", "coreos"), 0755) | ||||
| 	os.MkdirAll(path.Join(dir, "etc", "coreos"), 0755) | ||||
| 	os.MkdirAll(path.Join(dir, "run", "systemd", "system"), 0755) | ||||
|  | ||||
| 	ioutil.WriteFile(path.Join(dir, "usr", "share", "coreos", "update.conf"), []byte(base), 0644) | ||||
| } | ||||
|  | ||||
| func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) { | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(dir) | ||||
| 	setupFixtures(dir) | ||||
|  | ||||
| 	for i := 0; i < 2; i++ { | ||||
| 		if i == 1 { | ||||
| 			err = ioutil.WriteFile(path.Join(dir, "etc", "coreos", "update.conf"), []byte(configured), 0644) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if err := WriteLocksmithConfig("etcd-lock", dir); err != nil { | ||||
| 			t.Fatalf("Processing of LocksmithEnvironment failed: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		fullPath := path.Join(dir, "etc", "coreos", "update.conf") | ||||
|  | ||||
| 		fi, err := os.Stat(fullPath) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Unable to stat file: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		if fi.Mode() != os.FileMode(0644) { | ||||
| 			t.Errorf("File has incorrect mode: %v", fi.Mode()) | ||||
| 		} | ||||
|  | ||||
| 		contents, err := ioutil.ReadFile(fullPath) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Unable to read expected file: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		if string(contents) != expected { | ||||
| 			t.Fatalf("File has incorrect contents, got %v, wanted %v", string(contents), expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| func TestLocksmithEnvironmentMasked(t *testing.T) { | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(dir) | ||||
| 	setupFixtures(dir) | ||||
|  | ||||
| 	if err := WriteLocksmithConfig("off", dir); err != nil { | ||||
| 		t.Fatalf("Processing of LocksmithEnvironment failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service") | ||||
| 	target, err := os.Readlink(fullPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to read link %v", err) | ||||
| 	} | ||||
| 	if target != "/dev/null" { | ||||
| 		t.Fatalf("Locksmith not masked, unit target %v", target) | ||||
| 	} | ||||
| } | ||||
| @@ -165,3 +165,11 @@ func MachineID(root string) string { | ||||
|  | ||||
| 	return id | ||||
| } | ||||
|  | ||||
| func MaskUnit(unit string, root string) error { | ||||
| 	masked := path.Join(root, "etc", "systemd", "system", unit) | ||||
| 	if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return os.Symlink("/dev/null", masked) | ||||
| } | ||||
|   | ||||
| @@ -123,3 +123,22 @@ func TestMachineID(t *testing.T) { | ||||
| 		t.Fatalf("File has incorrect contents") | ||||
| 	} | ||||
| } | ||||
| func TestMaskUnit(t *testing.T) { | ||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tempdir: %v", err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(dir) | ||||
| 	if err := MaskUnit("foo.service", dir); err != nil { | ||||
| 		t.Fatalf("Unable to mask unit: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	fullPath := path.Join(dir, "etc", "systemd", "system", "foo.service") | ||||
| 	target, err := os.Readlink(fullPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to read link", err) | ||||
| 	} | ||||
| 	if target != "/dev/null" { | ||||
| 		t.Fatalf("unit not masked, got unit target", target) | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user