feat(etcd): Write etcd systemd snippet

This commit is contained in:
Brian Waldon 2014-03-17 15:09:59 -07:00
parent 0841173dfc
commit 137949f5ad
5 changed files with 160 additions and 30 deletions

View File

@ -74,12 +74,38 @@ Provide a list of objects with the following attributes:
## Custom cloud-config Parameters
### coreos.etcd.discovery_url
### coreos.etcd
The value of `coreos.etcd.discovery_url` will be used to discover the instance's etcd peers using the [etcd discovery protocol][disco-proto]. Usage of the [public discovery service][disco-service] is encouraged.
The `coreos.etcd.*` options are translated to a partial systemd unit acting as an etcd configuration file.
`coreos-cloudinit` will also replace the strings `$private_ipv4` and `$public_ipv4` with the values generated by CoreOS based on a given provider.
[disco-proto]: https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md
[disco-service]: http://discovery.etcd.io
For example, the following cloud-config document...
```
#cloud-config
coreos:
etcd:
name: node001
discovery-url: https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8
bind-addr: $public_ipv4:4001
peer-bind-addr: $private_ipv4:7001
```
...will generate a systemd snippet like this:
```
[Service]
Environment="ETCD_NAME=node001""
Environment="ETCD_DISCOVERY_URL=https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8"
Environment="ETCD_BIND_ADDR=203.0.113.29:4001"
Environment="ETCD_PEER_BIND_ADDR=192.0.2.13:7001"
```
For more information about the available configuration options, see the [etcd documentation][etcd-config].
Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
### coreos.units

View File

@ -12,7 +12,7 @@ const DefaultSSHKeyName = "coreos-cloudinit"
type CloudConfig struct {
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
Coreos struct {
Etcd struct{ Discovery_URL string }
Etcd EtcdEnvironment
Fleet struct{ Autostart bool }
Units []Unit
}
@ -98,13 +98,12 @@ func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error {
}
}
if cfg.Coreos.Etcd.Discovery_URL != "" {
err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL)
if err == nil {
log.Printf("Consumed etcd discovery url")
} else {
log.Fatalf("Failed to persist etcd discovery url to filesystem: %v", err)
if len(cfg.Coreos.Etcd) > 0 {
if err := WriteEtcdEnvironment("/", cfg.Coreos.Etcd); err != nil {
log.Fatalf("Failed to write etcd config to filesystem: %v", err)
}
log.Printf("Wrote etcd config file to filesystem")
}
if len(cfg.Coreos.Units) > 0 {

View File

@ -17,10 +17,6 @@ func TestCloudConfigEmpty(t *testing.T) {
t.Error("Parsed incorrect number of SSH keys")
}
if cfg.Coreos.Etcd.Discovery_URL != "" {
t.Error("Parsed incorrect value of discovery url")
}
if cfg.Coreos.Fleet.Autostart {
t.Error("Expected AutostartFleet not to be defined")
}
@ -81,10 +77,6 @@ hostname: trontastic
t.Error("Expected first SSH key to be 'foobaz'")
}
if cfg.Coreos.Etcd.Discovery_URL != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
t.Error("Failed to parse etcd discovery url")
}
if !cfg.Coreos.Fleet.Autostart {
t.Error("Expected AutostartFleet to be true")
}

View File

@ -1,25 +1,48 @@
package cloudinit
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"strings"
)
const (
etcdDiscoveryPath = "/var/run/etcd/bootstrap.disco"
)
type EtcdEnvironment map[string]string
func PersistEtcdDiscoveryURL(url string) error {
dir := path.Dir(etcdDiscoveryPath)
if _, err := os.Stat(dir); err != nil {
log.Printf("Creating directory /var/run/etcd")
err := os.MkdirAll(dir, os.FileMode(0644))
if err != nil {
func (ec EtcdEnvironment) String() (out string) {
public := os.Getenv("COREOS_PUBLIC_IPV4")
private := os.Getenv("COREOS_PRIVATE_IPV4")
out += "[Service]\n"
for key, val := range ec {
key = strings.ToUpper(key)
key = strings.Replace(key, "-", "_", -1)
if public != "" {
val = strings.Replace(val, "$public_ipv4", public, -1)
}
if private != "" {
val = strings.Replace(val, "$private_ipv4", private, -1)
}
out += fmt.Sprintf("Environment=\"ETCD_%s=%s\"\n", key, val)
}
return
}
// Write an EtcdEnvironment to the appropriate path on disk for etcd.service
func WriteEtcdEnvironment(root string, env EtcdEnvironment) error {
cfgDir := path.Join(root, "etc", "systemd", "system", "etcd.service.d")
cfgFile := path.Join(cfgDir, "20-cloudinit.conf")
if _, err := os.Stat(cfgDir); err != nil {
if err := os.MkdirAll(cfgDir, os.FileMode(0755)); err != nil {
return err
}
}
return ioutil.WriteFile(etcdDiscoveryPath, []byte(url), os.FileMode(0644))
return ioutil.WriteFile(cfgFile, []byte(env.String()), os.FileMode(0644))
}

90
cloudinit/etcd_test.go Normal file
View File

@ -0,0 +1,90 @@
package cloudinit
import (
"io/ioutil"
"os"
"os/exec"
"path"
"syscall"
"testing"
)
func TestEtcdEnvironment(t *testing.T) {
cfg := make(EtcdEnvironment, 0)
cfg["discovery_url"] = "http://disco.example.com/foobar"
cfg["peer-bind-addr"] = "127.0.0.1:7002"
env := cfg.String()
expect := `[Service]
Environment="ETCD_DISCOVERY_URL=http://disco.example.com/foobar"
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
`
if env != expect {
t.Errorf("Generated environment:\n%s\nExpected environment:\n%s", env, expect)
}
}
func TestEtcdEnvironmentReplacement(t *testing.T) {
os.Clearenv()
os.Setenv("COREOS_PUBLIC_IPV4", "203.0.113.29")
os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.13")
cfg := make(EtcdEnvironment, 0)
cfg["bind-addr"] = "$public_ipv4:4001"
cfg["peer-bind-addr"] = "$private_ipv4:7001"
env := cfg.String()
expect := `[Service]
Environment="ETCD_BIND_ADDR=203.0.113.29:4001"
Environment="ETCD_PEER_BIND_ADDR=192.0.2.13:7001"
`
if env != expect {
t.Errorf("Generated environment:\n%s\nExpected environment:\n%s", env, expect)
}
}
func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
ec := EtcdEnvironment{
"discovery_url": "http://disco.example.com/foobar",
"peer-bind-addr": "127.0.0.1:7002",
}
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
if err := WriteEtcdEnvironment(dir, ec); err != nil {
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
}
fullPath := path.Join(dir, "etc", "systemd", "system", "etcd.service.d", "20-cloudinit.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)
}
expect := `[Service]
Environment="ETCD_DISCOVERY_URL=http://disco.example.com/foobar"
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
`
if string(contents) != expect {
t.Fatalf("File has incorrect contents")
}
}
func rmdir(path string) error {
cmd := exec.Command("rm", "-rf", path)
return cmd.Run()
}