Merge pull request #96 from philips/locksmith-support
Add locksmith support v2
This commit is contained in:
commit
799c02865c
@ -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
|
[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
|
#### 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:
|
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:
|
||||||
|
@ -14,6 +14,7 @@ type CloudConfig struct {
|
|||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
Coreos struct {
|
Coreos struct {
|
||||||
Etcd EtcdEnvironment
|
Etcd EtcdEnvironment
|
||||||
|
Update map[string]string
|
||||||
Units []system.Unit
|
Units []system.Unit
|
||||||
OEM OEMRelease
|
OEM OEMRelease
|
||||||
}
|
}
|
||||||
@ -128,6 +129,13 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
log.Printf("Wrote etcd config file to filesystem")
|
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 {
|
if len(cfg.Coreos.Units) > 0 {
|
||||||
commands := make(map[string]string, 0)
|
commands := make(map[string]string, 0)
|
||||||
for _, unit := range cfg.Coreos.Units {
|
for _, unit := range cfg.Coreos.Units {
|
||||||
|
@ -32,6 +32,8 @@ func TestCloudConfig(t *testing.T) {
|
|||||||
coreos:
|
coreos:
|
||||||
etcd:
|
etcd:
|
||||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||||
|
update:
|
||||||
|
reboot-strategy: reboot
|
||||||
units:
|
units:
|
||||||
- name: 50-eth0.network
|
- name: 50-eth0.network
|
||||||
runtime: yes
|
runtime: yes
|
||||||
@ -129,6 +131,9 @@ Address=10.209.171.177/19
|
|||||||
if cfg.Hostname != "trontastic" {
|
if cfg.Hostname != "trontastic" {
|
||||||
t.Errorf("Failed to parse hostname")
|
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
|
// 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
|
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")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user