Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
364507fb75 | ||
|
08d4842502 | ||
|
21e32e44f8 | ||
|
7a06dee16f | ||
|
ff9cf5743d | ||
|
1b10a3a187 | ||
|
10838e001d | ||
|
96370ac5b9 | ||
|
0b82cd074d | ||
|
a974e85103 | ||
|
f0450662b0 | ||
|
03e29d1291 | ||
|
98ae5d88aa | ||
|
be51f4eba0 | ||
|
e3037f18a6 | ||
|
fe388a3ab6 | ||
|
c820f2b1cf |
@@ -13,7 +13,7 @@ If no **id** field is provided, coreos-cloudinit will ignore this section.
|
|||||||
|
|
||||||
For example, the following cloud-config document...
|
For example, the following cloud-config document...
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
coreos:
|
coreos:
|
||||||
oem:
|
oem:
|
||||||
@@ -26,7 +26,7 @@ coreos:
|
|||||||
|
|
||||||
...would be rendered to the following `/etc/oem-release`:
|
...would be rendered to the following `/etc/oem-release`:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
ID=rackspace
|
ID=rackspace
|
||||||
NAME="Rackspace Cloud Servers"
|
NAME="Rackspace Cloud Servers"
|
||||||
VERSION_ID=168.0.0
|
VERSION_ID=168.0.0
|
||||||
|
@@ -42,7 +42,7 @@ CoreOS tries to conform to each platform's native method to provide user data. E
|
|||||||
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||||
We can use the templating feature of coreos-cloudinit to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
|
We can use the templating feature of coreos-cloudinit to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
@@ -57,7 +57,7 @@ coreos:
|
|||||||
|
|
||||||
...will generate a systemd unit drop-in like this:
|
...will generate a systemd unit drop-in like this:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
[Service]
|
[Service]
|
||||||
Environment="ETCD_NAME=node001"
|
Environment="ETCD_NAME=node001"
|
||||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||||
@@ -74,7 +74,7 @@ Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
|
|||||||
|
|
||||||
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
@@ -85,7 +85,7 @@ coreos:
|
|||||||
|
|
||||||
...will generate a systemd unit drop-in like this:
|
...will generate a systemd unit drop-in like this:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
[Service]
|
[Service]
|
||||||
Environment="FLEET_PUBLIC_IP=203.0.113.29"
|
Environment="FLEET_PUBLIC_IP=203.0.113.29"
|
||||||
Environment="FLEET_METADATA=region=us-west"
|
Environment="FLEET_METADATA=region=us-west"
|
||||||
@@ -114,7 +114,7 @@ The `reboot-strategy` parameter also affects the behaviour of [locksmith](https:
|
|||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
coreos:
|
coreos:
|
||||||
update:
|
update:
|
||||||
@@ -138,7 +138,7 @@ The `coreos.units.*` parameters define a list of arbitrary systemd units to star
|
|||||||
|
|
||||||
Write a unit to disk, automatically starting it.
|
Write a unit to disk, automatically starting it.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
@@ -159,7 +159,7 @@ coreos:
|
|||||||
|
|
||||||
Start the built-in `etcd` and `fleet` services:
|
Start the built-in `etcd` and `fleet` services:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
@@ -177,7 +177,7 @@ The `ssh_authorized_keys` parameter adds public SSH keys which will be authorize
|
|||||||
The keys will be named "coreos-cloudinit" by default.
|
The keys will be named "coreos-cloudinit" by default.
|
||||||
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
|
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
ssh_authorized_keys:
|
ssh_authorized_keys:
|
||||||
@@ -189,7 +189,7 @@ ssh_authorized_keys:
|
|||||||
The `hostname` parameter defines the system's hostname.
|
The `hostname` parameter defines the system's hostname.
|
||||||
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
|
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
hostname: coreos1
|
hostname: coreos1
|
||||||
@@ -222,7 +222,7 @@ The following fields are not yet implemented:
|
|||||||
- **selinux-user**: Corresponding SELinux user
|
- **selinux-user**: Corresponding SELinux user
|
||||||
- **ssh-import-id**: Import SSH keys by ID from Launchpad.
|
- **ssh-import-id**: Import SSH keys by ID from Launchpad.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
users:
|
users:
|
||||||
@@ -261,7 +261,7 @@ Using a higher number of rounds will help create more secure passwords, but give
|
|||||||
|
|
||||||
Using the `coreos-ssh-import-github` field, we can import public SSH keys from a GitHub user to use as authorized keys to a server.
|
Using the `coreos-ssh-import-github` field, we can import public SSH keys from a GitHub user to use as authorized keys to a server.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
users:
|
users:
|
||||||
@@ -274,7 +274,7 @@ users:
|
|||||||
We can also pull public SSH keys from any HTTP endpoint which matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
|
We can also pull public SSH keys from any HTTP endpoint which matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
|
||||||
For example, if you have an installation of GitHub Enterprise, you can provide a complete URL with an authentication token:
|
For example, if you have an installation of GitHub Enterprise, you can provide a complete URL with an authentication token:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
users:
|
users:
|
||||||
@@ -284,7 +284,7 @@ users:
|
|||||||
|
|
||||||
You can also specify any URL whose response matches the JSON format for public keys:
|
You can also specify any URL whose response matches the JSON format for public keys:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
users:
|
users:
|
||||||
@@ -304,7 +304,7 @@ The `write-file` parameter defines a list of files to create on the local filesy
|
|||||||
Explicitly not implemented is the **encoding** attribute.
|
Explicitly not implemented is the **encoding** attribute.
|
||||||
The **content** field must represent exactly what should be written to disk.
|
The **content** field must represent exactly what should be written to disk.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
write_files:
|
write_files:
|
||||||
- path: /etc/fleet/fleet.conf
|
- path: /etc/fleet/fleet.conf
|
||||||
@@ -321,7 +321,7 @@ Currently, the only supported value is "localhost" which will cause your system'
|
|||||||
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
|
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
|
||||||
infrastructure in place to resolve its own hostname, for example, when using Vagrant.
|
infrastructure in place to resolve its own hostname, for example, when using Vagrant.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
manage_etc_hosts: localhost
|
manage_etc_hosts: localhost
|
||||||
|
@@ -14,17 +14,21 @@ The image should be a single FAT or ISO9660 file system with the label
|
|||||||
|
|
||||||
For example, to wrap up a config named `user_data` in a config drive image:
|
For example, to wrap up a config named `user_data` in a config drive image:
|
||||||
|
|
||||||
mkdir -p /tmp/new-drive/openstack/latest
|
```sh
|
||||||
cp user_data /tmp/new-drive/openstack/latest/user_data
|
mkdir -p /tmp/new-drive/openstack/latest
|
||||||
mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
|
cp user_data /tmp/new-drive/openstack/latest/user_data
|
||||||
rm -r /tmp/new-drive
|
mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
|
||||||
|
rm -r /tmp/new-drive
|
||||||
|
```
|
||||||
|
|
||||||
## QEMU virtfs
|
## QEMU virtfs
|
||||||
|
|
||||||
One exception to the above, when using QEMU it is possible to skip creating an
|
One exception to the above, when using QEMU it is possible to skip creating an
|
||||||
image and use a plain directory containing the same contents:
|
image and use a plain directory containing the same contents:
|
||||||
|
|
||||||
qemu-system-x86_64 \
|
```sh
|
||||||
-fsdev local,id=conf,security_model=none,readonly,path=/tmp/new-drive \
|
qemu-system-x86_64 \
|
||||||
-device virtio-9p-pci,fsdev=conf,mount_tag=config-2 \
|
-fsdev local,id=conf,security_model=none,readonly,path=/tmp/new-drive \
|
||||||
[usual qemu options here...]
|
-device virtio-9p-pci,fsdev=conf,mount_tag=config-2 \
|
||||||
|
[usual qemu options here...]
|
||||||
|
```
|
||||||
|
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "0.8.9"
|
version = "0.9.0"
|
||||||
datasourceInterval = 100 * time.Millisecond
|
datasourceInterval = 100 * time.Millisecond
|
||||||
datasourceMaxInterval = 30 * time.Second
|
datasourceMaxInterval = 30 * time.Second
|
||||||
datasourceTimeout = 5 * time.Minute
|
datasourceTimeout = 5 * time.Minute
|
||||||
|
@@ -272,13 +272,23 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
um := system.NewUnitManager(env.Root())
|
||||||
|
return processUnits(cfg.Coreos.Units, env.Root(), um)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUnits takes a set of Units and applies them to the given root using
|
||||||
|
// the given UnitManager. This can involve things like writing unit files to
|
||||||
|
// disk, masking/unmasking units, or invoking systemd
|
||||||
|
// commands against units. It returns any error encountered.
|
||||||
|
func processUnits(units []system.Unit, root string, um system.UnitManager) error {
|
||||||
commands := make(map[string]string, 0)
|
commands := make(map[string]string, 0)
|
||||||
reload := false
|
reload := false
|
||||||
for _, unit := range cfg.Coreos.Units {
|
for _, unit := range units {
|
||||||
dst := unit.Destination(env.Root())
|
dst := unit.Destination(root)
|
||||||
if unit.Content != "" {
|
if unit.Content != "" {
|
||||||
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
||||||
if err := system.PlaceUnit(&unit, dst); err != nil {
|
if err := um.PlaceUnit(&unit, dst); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
||||||
@@ -287,12 +297,12 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
|
|
||||||
if unit.Mask {
|
if unit.Mask {
|
||||||
log.Printf("Masking unit file %s", unit.Name)
|
log.Printf("Masking unit file %s", unit.Name)
|
||||||
if err := system.MaskUnit(&unit, env.Root()); err != nil {
|
if err := um.MaskUnit(&unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if unit.Runtime {
|
} else if unit.Runtime {
|
||||||
log.Printf("Ensuring runtime unit file %s is unmasked", unit.Name)
|
log.Printf("Ensuring runtime unit file %s is unmasked", unit.Name)
|
||||||
if err := system.UnmaskUnit(&unit, env.Root()); err != nil {
|
if err := um.UnmaskUnit(&unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +310,7 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
if unit.Enable {
|
if unit.Enable {
|
||||||
if unit.Group() != "network" {
|
if unit.Group() != "network" {
|
||||||
log.Printf("Enabling unit file %s", unit.Name)
|
log.Printf("Enabling unit file %s", unit.Name)
|
||||||
if err := system.EnableUnitFile(unit.Name, unit.Runtime); err != nil {
|
if err := um.EnableUnitFile(unit.Name, unit.Runtime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Enabled unit %s", unit.Name)
|
log.Printf("Enabled unit %s", unit.Name)
|
||||||
@@ -317,14 +327,14 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reload {
|
if reload {
|
||||||
if err := system.DaemonReload(); err != nil {
|
if err := um.DaemonReload(); err != nil {
|
||||||
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for unit, command := range commands {
|
for unit, command := range commands {
|
||||||
log.Printf("Calling unit command '%s %s'", command, unit)
|
log.Printf("Calling unit command '%s %s'", command, unit)
|
||||||
res, err := system.RunUnitCommand(command, unit)
|
res, err := um.RunUnitCommand(command, unit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCloudConfigUnknownKeys(t *testing.T) {
|
func TestCloudConfigUnknownKeys(t *testing.T) {
|
||||||
@@ -332,3 +334,109 @@ users:
|
|||||||
t.Errorf("Failed to parse no-log-init field")
|
t.Errorf("Failed to parse no-log-init field")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestUnitManager struct {
|
||||||
|
placed []string
|
||||||
|
enabled []string
|
||||||
|
masked []string
|
||||||
|
unmasked []string
|
||||||
|
commands map[string]string
|
||||||
|
reload bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tum *TestUnitManager) PlaceUnit(unit *system.Unit, dst string) error {
|
||||||
|
tum.placed = append(tum.placed, unit.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) EnableUnitFile(unit string, runtime bool) error {
|
||||||
|
tum.enabled = append(tum.enabled, unit)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) RunUnitCommand(command, unit string) (string, error) {
|
||||||
|
tum.commands = make(map[string]string)
|
||||||
|
tum.commands[unit] = command
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) DaemonReload() error {
|
||||||
|
tum.reload = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) MaskUnit(unit *system.Unit) error {
|
||||||
|
tum.masked = append(tum.masked, unit.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) UnmaskUnit(unit *system.Unit) error {
|
||||||
|
tum.unmasked = append(tum.unmasked, unit.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessUnits(t *testing.T) {
|
||||||
|
tum := &TestUnitManager{}
|
||||||
|
units := []system.Unit{
|
||||||
|
system.Unit{
|
||||||
|
Name: "foo",
|
||||||
|
Mask: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := processUnits(units, "", tum); err != nil {
|
||||||
|
t.Fatalf("unexpected error calling processUnits: %v", err)
|
||||||
|
}
|
||||||
|
if len(tum.masked) != 1 || tum.masked[0] != "foo" {
|
||||||
|
t.Errorf("expected foo to be masked, but found %v", tum.masked)
|
||||||
|
}
|
||||||
|
|
||||||
|
tum = &TestUnitManager{}
|
||||||
|
units = []system.Unit{
|
||||||
|
system.Unit{
|
||||||
|
Name: "bar.network",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := processUnits(units, "", tum); err != nil {
|
||||||
|
t.Fatalf("unexpected error calling processUnits: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := tum.commands["systemd-networkd.service"]; !ok {
|
||||||
|
t.Errorf("expected systemd-networkd.service to be reloaded!")
|
||||||
|
}
|
||||||
|
|
||||||
|
tum = &TestUnitManager{}
|
||||||
|
units = []system.Unit{
|
||||||
|
system.Unit{
|
||||||
|
Name: "baz.service",
|
||||||
|
Content: "[Service]\nExecStart=/bin/true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := processUnits(units, "", tum); err != nil {
|
||||||
|
t.Fatalf("unexpected error calling processUnits: %v", err)
|
||||||
|
}
|
||||||
|
if len(tum.placed) != 1 || tum.placed[0] != "baz.service" {
|
||||||
|
t.Fatalf("expected baz.service to be written, but got %v", tum.placed)
|
||||||
|
}
|
||||||
|
|
||||||
|
tum = &TestUnitManager{}
|
||||||
|
units = []system.Unit{
|
||||||
|
system.Unit{
|
||||||
|
Name: "locksmithd.service",
|
||||||
|
Runtime: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := processUnits(units, "", tum); err != nil {
|
||||||
|
t.Fatalf("unexpected error calling processUnits: %v", err)
|
||||||
|
}
|
||||||
|
if len(tum.unmasked) != 1 || tum.unmasked[0] != "locksmithd.service" {
|
||||||
|
t.Fatalf("expected locksmithd.service to be unmasked, but got %v", tum.unmasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
tum = &TestUnitManager{}
|
||||||
|
units = []system.Unit{
|
||||||
|
system.Unit{
|
||||||
|
Name: "woof",
|
||||||
|
Enable: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := processUnits(units, "", tum); err != nil {
|
||||||
|
t.Fatalf("unexpected error calling processUnits: %v", err)
|
||||||
|
}
|
||||||
|
if len(tum.enabled) != 1 || tum.enabled[0] != "woof" {
|
||||||
|
t.Fatalf("expected woof to be enabled, but got %v", tum.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -70,6 +70,8 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := system.NewUnitManager(dir)
|
||||||
|
|
||||||
uu, err := ee.Units(dir)
|
uu, err := ee.Units(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Generating etcd unit failed: %v", err)
|
t.Fatalf("Generating etcd unit failed: %v", err)
|
||||||
@@ -81,7 +83,7 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
|||||||
|
|
||||||
dst := u.Destination(dir)
|
dst := u.Destination(dir)
|
||||||
os.Stderr.WriteString("writing to " + dir + "\n")
|
os.Stderr.WriteString("writing to " + dir + "\n")
|
||||||
if err := system.PlaceUnit(&u, dst); err != nil {
|
if err := sd.PlaceUnit(&u, dst); err != nil {
|
||||||
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +121,8 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := system.NewUnitManager(dir)
|
||||||
|
|
||||||
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
|
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
|
||||||
err = ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007"), os.FileMode(0444))
|
err = ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007"), os.FileMode(0444))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -136,7 +140,7 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
|||||||
|
|
||||||
dst := u.Destination(dir)
|
dst := u.Destination(dir)
|
||||||
os.Stderr.WriteString("writing to " + dir + "\n")
|
os.Stderr.WriteString("writing to " + dir + "\n")
|
||||||
if err := system.PlaceUnit(&u, dst); err != nil {
|
if err := sd.PlaceUnit(&u, dst); err != nil {
|
||||||
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ package network
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InterfaceGenerator interface {
|
type InterfaceGenerator interface {
|
||||||
@@ -11,6 +12,8 @@ type InterfaceGenerator interface {
|
|||||||
Netdev() string
|
Netdev() string
|
||||||
Link() string
|
Link() string
|
||||||
Network() string
|
Network() string
|
||||||
|
Type() string
|
||||||
|
ModprobeParams() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type networkInterface interface {
|
type networkInterface interface {
|
||||||
@@ -68,6 +71,10 @@ func (i *logicalInterface) Children() []networkInterface {
|
|||||||
return i.children
|
return i.children
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) ModprobeParams() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) setConfigDepth(depth int) {
|
func (i *logicalInterface) setConfigDepth(depth int) {
|
||||||
i.configDepth = depth
|
i.configDepth = depth
|
||||||
}
|
}
|
||||||
@@ -84,9 +91,14 @@ func (p *physicalInterface) Netdev() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *physicalInterface) Type() string {
|
||||||
|
return "physical"
|
||||||
|
}
|
||||||
|
|
||||||
type bondInterface struct {
|
type bondInterface struct {
|
||||||
logicalInterface
|
logicalInterface
|
||||||
slaves []string
|
slaves []string
|
||||||
|
options map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bondInterface) Name() string {
|
func (b *bondInterface) Name() string {
|
||||||
@@ -97,6 +109,19 @@ func (b *bondInterface) Netdev() string {
|
|||||||
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bondInterface) Type() string {
|
||||||
|
return "bond"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bondInterface) ModprobeParams() string {
|
||||||
|
params := ""
|
||||||
|
for name, val := range b.options {
|
||||||
|
params += fmt.Sprintf("%s=%s ", name, val)
|
||||||
|
}
|
||||||
|
params = strings.TrimSuffix(params, " ")
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
type vlanInterface struct {
|
type vlanInterface struct {
|
||||||
logicalInterface
|
logicalInterface
|
||||||
id int
|
id int
|
||||||
@@ -123,6 +148,10 @@ func (v *vlanInterface) Netdev() string {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *vlanInterface) Type() string {
|
||||||
|
return "vlan"
|
||||||
|
}
|
||||||
|
|
||||||
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
||||||
interfaceMap := createInterfaces(stanzas)
|
interfaceMap := createInterfaces(stanzas)
|
||||||
linkAncestors(interfaceMap)
|
linkAncestors(interfaceMap)
|
||||||
@@ -141,15 +170,22 @@ func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
|
|||||||
for _, iface := range stanzas {
|
for _, iface := range stanzas {
|
||||||
switch iface.kind {
|
switch iface.kind {
|
||||||
case interfaceBond:
|
case interfaceBond:
|
||||||
|
bondOptions := make(map[string]string)
|
||||||
|
for _, k := range []string{"mode", "miimon", "lacp-rate"} {
|
||||||
|
if v, ok := iface.options["bond-"+k]; ok && len(v) > 0 {
|
||||||
|
bondOptions[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
interfaceMap[iface.name] = &bondInterface{
|
interfaceMap[iface.name] = &bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: iface.name,
|
name: iface.name,
|
||||||
config: iface.configMethod,
|
config: iface.configMethod,
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
},
|
},
|
||||||
iface.options["slaves"],
|
iface.options["bond-slaves"],
|
||||||
|
bondOptions,
|
||||||
}
|
}
|
||||||
for _, slave := range iface.options["slaves"] {
|
for _, slave := range iface.options["bond-slaves"] {
|
||||||
if _, ok := interfaceMap[slave]; !ok {
|
if _, ok := interfaceMap[slave]; !ok {
|
||||||
interfaceMap[slave] = &physicalInterface{
|
interfaceMap[slave] = &physicalInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
@@ -241,13 +277,17 @@ func markConfigDepths(interfaceMap map[string]networkInterface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, iface := range rootInterfaceMap {
|
for _, iface := range rootInterfaceMap {
|
||||||
setDepth(iface, 0)
|
setDepth(iface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDepth(iface networkInterface, depth int) {
|
func setDepth(iface networkInterface) int {
|
||||||
iface.setConfigDepth(depth)
|
maxDepth := 0
|
||||||
for _, child := range iface.Children() {
|
for _, child := range iface.Children() {
|
||||||
setDepth(child, depth+1)
|
if depth := setDepth(child); depth > maxDepth {
|
||||||
|
maxDepth = depth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
iface.setConfigDepth(maxDepth)
|
||||||
|
return (maxDepth + 1)
|
||||||
}
|
}
|
||||||
|
@@ -36,6 +36,7 @@ func TestPhysicalInterfaceNetwork(t *testing.T) {
|
|||||||
name: "testbond1",
|
name: "testbond1",
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
&vlanInterface{
|
&vlanInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
@@ -67,14 +68,14 @@ VLAN=testvlan2
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBondInterfaceName(t *testing.T) {
|
func TestBondInterfaceName(t *testing.T) {
|
||||||
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
|
||||||
if b.Name() != "testname" {
|
if b.Name() != "testname" {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBondInterfaceNetdev(t *testing.T) {
|
func TestBondInterfaceNetdev(t *testing.T) {
|
||||||
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
|
||||||
netdev := `[NetDev]
|
netdev := `[NetDev]
|
||||||
Kind=bond
|
Kind=bond
|
||||||
Name=testname
|
Name=testname
|
||||||
@@ -102,6 +103,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
|
|||||||
name: "testbond1",
|
name: "testbond1",
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
&vlanInterface{
|
&vlanInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
@@ -120,6 +122,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
}
|
}
|
||||||
network := `[Match]
|
network := `[Match]
|
||||||
Name=testname
|
Name=testname
|
||||||
@@ -218,6 +221,61 @@ Gateway=1.2.3.4
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestType(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
i InterfaceGenerator
|
||||||
|
t string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
i: &physicalInterface{},
|
||||||
|
t: "physical",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: &vlanInterface{},
|
||||||
|
t: "vlan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: &bondInterface{},
|
||||||
|
t: "bond",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if tp := tt.i.Type(); tp != tt.t {
|
||||||
|
t.Fatalf("bad type (%q): got %s, want %s", tt.i, tp, tt.t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModprobeParams(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
i InterfaceGenerator
|
||||||
|
p string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
i: &physicalInterface{},
|
||||||
|
p: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: &vlanInterface{},
|
||||||
|
p: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: &bondInterface{
|
||||||
|
logicalInterface{},
|
||||||
|
nil,
|
||||||
|
map[string]string{
|
||||||
|
"a": "1",
|
||||||
|
"b": "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: "a=1 b=2",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if p := tt.i.ModprobeParams(); p != tt.p {
|
||||||
|
t.Fatalf("bad params (%q): got %s, want %s", tt.i, p, tt.p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildInterfacesLo(t *testing.T) {
|
func TestBuildInterfacesLo(t *testing.T) {
|
||||||
stanzas := []*stanzaInterface{
|
stanzas := []*stanzaInterface{
|
||||||
&stanzaInterface{
|
&stanzaInterface{
|
||||||
@@ -242,7 +300,7 @@ func TestBuildInterfacesBlindBond(t *testing.T) {
|
|||||||
auto: false,
|
auto: false,
|
||||||
configMethod: configMethodManual{},
|
configMethod: configMethodManual{},
|
||||||
options: map[string][]string{
|
options: map[string][]string{
|
||||||
"slaves": []string{"eth0"},
|
"bond-slaves": []string{"eth0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -252,16 +310,17 @@ func TestBuildInterfacesBlindBond(t *testing.T) {
|
|||||||
name: "bond0",
|
name: "bond0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
configDepth: 1,
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
[]string{"eth0"},
|
[]string{"eth0"},
|
||||||
|
map[string]string{},
|
||||||
}
|
}
|
||||||
eth0 := &physicalInterface{
|
eth0 := &physicalInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "eth0",
|
name: "eth0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{bond0},
|
children: []networkInterface{bond0},
|
||||||
configDepth: 0,
|
configDepth: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect := []InterfaceGenerator{bond0, eth0}
|
expect := []InterfaceGenerator{bond0, eth0}
|
||||||
@@ -289,7 +348,7 @@ func TestBuildInterfacesBlindVLAN(t *testing.T) {
|
|||||||
name: "vlan0",
|
name: "vlan0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
configDepth: 1,
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
"eth0",
|
"eth0",
|
||||||
@@ -299,7 +358,7 @@ func TestBuildInterfacesBlindVLAN(t *testing.T) {
|
|||||||
name: "eth0",
|
name: "eth0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{vlan0},
|
children: []networkInterface{vlan0},
|
||||||
configDepth: 0,
|
configDepth: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect := []InterfaceGenerator{eth0, vlan0}
|
expect := []InterfaceGenerator{eth0, vlan0}
|
||||||
@@ -323,7 +382,9 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
auto: false,
|
auto: false,
|
||||||
configMethod: configMethodManual{},
|
configMethod: configMethodManual{},
|
||||||
options: map[string][]string{
|
options: map[string][]string{
|
||||||
"slaves": []string{"eth0"},
|
"bond-slaves": []string{"eth0"},
|
||||||
|
"bond-mode": []string{"4"},
|
||||||
|
"bond-miimon": []string{"100"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&stanzaInterface{
|
&stanzaInterface{
|
||||||
@@ -332,7 +393,7 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
auto: false,
|
auto: false,
|
||||||
configMethod: configMethodManual{},
|
configMethod: configMethodManual{},
|
||||||
options: map[string][]string{
|
options: map[string][]string{
|
||||||
"slaves": []string{"bond0"},
|
"bond-slaves": []string{"bond0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&stanzaInterface{
|
&stanzaInterface{
|
||||||
@@ -362,7 +423,7 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
name: "vlan1",
|
name: "vlan1",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
configDepth: 2,
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
1,
|
1,
|
||||||
"bond0",
|
"bond0",
|
||||||
@@ -372,7 +433,7 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
name: "vlan0",
|
name: "vlan0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
configDepth: 1,
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
"eth0",
|
"eth0",
|
||||||
@@ -382,9 +443,10 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
name: "bond1",
|
name: "bond1",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{},
|
children: []networkInterface{},
|
||||||
configDepth: 2,
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
[]string{"bond0"},
|
[]string{"bond0"},
|
||||||
|
map[string]string{},
|
||||||
}
|
}
|
||||||
bond0 := &bondInterface{
|
bond0 := &bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
@@ -394,13 +456,17 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
configDepth: 1,
|
configDepth: 1,
|
||||||
},
|
},
|
||||||
[]string{"eth0"},
|
[]string{"eth0"},
|
||||||
|
map[string]string{
|
||||||
|
"mode": "4",
|
||||||
|
"miimon": "100",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
eth0 := &physicalInterface{
|
eth0 := &physicalInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "eth0",
|
name: "eth0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []networkInterface{bond0, vlan0},
|
children: []networkInterface{bond0, vlan0},
|
||||||
configDepth: 0,
|
configDepth: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
||||||
|
@@ -293,7 +293,6 @@ func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||||
options["slaves"] = options["bond-slaves"]
|
|
||||||
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -129,7 +129,7 @@ func TestParseBondStanzaNoSlaves(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if bond.options["slaves"] != nil {
|
if bond.options["bond-slaves"] != nil {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,9 +152,6 @@ func TestParseBondStanza(t *testing.T) {
|
|||||||
if bond.configMethod != conf {
|
if bond.configMethod != conf {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(bond.options["slaves"], options["bond-slaves"]) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePhysicalStanza(t *testing.T) {
|
func TestParsePhysicalStanza(t *testing.T) {
|
||||||
|
@@ -44,7 +44,7 @@ func TestWriteEnvFileUpdate(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -70,11 +70,11 @@ func TestWriteEnvFileUpdate(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was not replaced: %s", fullPath)
|
t.Fatalf("File was not replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func TestWriteEnvFileUpdateNoNewline(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -117,11 +117,11 @@ func TestWriteEnvFileUpdateNoNewline(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was not replaced: %s", fullPath)
|
t.Fatalf("File was not replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ func TestWriteEnvFileNoop(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -196,11 +196,11 @@ func TestWriteEnvFileNoop(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino != newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino != newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was replaced: %s", fullPath)
|
t.Fatalf("File was replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ func TestWriteEnvFileUpdateDos(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -243,11 +243,11 @@ func TestWriteEnvFileUpdateDos(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was not replaced: %s", fullPath)
|
t.Fatalf("File was not replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ func TestWriteEnvFileDos2Unix(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -292,11 +292,11 @@ func TestWriteEnvFileDos2Unix(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino == newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was not replaced: %s", fullPath)
|
t.Fatalf("File was not replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ func TestWriteEnvFileEmpty(t *testing.T) {
|
|||||||
|
|
||||||
oldStat, err := os.Stat(fullPath)
|
oldStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := EnvFile{
|
ef := EnvFile{
|
||||||
@@ -340,11 +340,11 @@ func TestWriteEnvFileEmpty(t *testing.T) {
|
|||||||
|
|
||||||
newStat, err := os.Stat(fullPath)
|
newStat, err := os.Stat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to stat file: %v", err)
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldStat.Sys().(*syscall.Stat_t).Ino != newStat.Sys().(*syscall.Stat_t).Ino {
|
if oldStat.Sys().(*syscall.Stat_t).Ino != newStat.Sys().(*syscall.Stat_t).Ino {
|
||||||
t.Fatal("File was replaced: %s", fullPath)
|
t.Fatalf("File was replaced: %s", fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,10 +2,11 @@ package system
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/network"
|
"github.com/coreos/coreos-cloudinit/network"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink"
|
"github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink"
|
||||||
@@ -17,6 +18,13 @@ const (
|
|||||||
|
|
||||||
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if e := restartNetworkd(); e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(crawford): Get rid of this once networkd fixes the race
|
||||||
|
// https://bugs.freedesktop.org/show_bug.cgi?id=76077
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
if e := restartNetworkd(); e != nil {
|
if e := restartNetworkd(); e != nil {
|
||||||
err = e
|
err = e
|
||||||
}
|
}
|
||||||
@@ -26,19 +34,18 @@ func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = probe8012q(); err != nil {
|
if err = maybeProbe8012q(interfaces); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return maybeProbeBonding(interfaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
||||||
sysInterfaceMap := make(map[string]*net.Interface)
|
sysInterfaceMap := make(map[string]*net.Interface)
|
||||||
if systemInterfaces, err := net.Interfaces(); err == nil {
|
if systemInterfaces, err := net.Interfaces(); err == nil {
|
||||||
for _, iface := range systemInterfaces {
|
for _, iface := range systemInterfaces {
|
||||||
// Need a copy of the interface so we can take the address
|
iface := iface
|
||||||
temp := iface
|
sysInterfaceMap[iface.Name] = &iface
|
||||||
sysInterfaceMap[temp.Name] = &temp
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
@@ -46,6 +53,7 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
|||||||
|
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
|
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
|
||||||
|
log.Printf("Taking down interface %q\n", systemInterface.Name)
|
||||||
if err := netlink.NetworkLinkDown(systemInterface); err != nil {
|
if err := netlink.NetworkLinkDown(systemInterface); err != nil {
|
||||||
fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
|
fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
|
||||||
}
|
}
|
||||||
@@ -55,26 +63,45 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func probe8012q() error {
|
func maybeProbe8012q(interfaces []network.InterfaceGenerator) error {
|
||||||
return exec.Command("modprobe", "8021q").Run()
|
for _, iface := range interfaces {
|
||||||
|
if iface.Type() == "vlan" {
|
||||||
|
log.Printf("Probing LKM %q (%q)\n", "8021q", "8021q")
|
||||||
|
return exec.Command("modprobe", "8021q").Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
|
||||||
|
args := []string{"bonding"}
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if iface.Type() == "bond" {
|
||||||
|
args = append(args, strings.Split(iface.ModprobeParams(), " ")...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Probing LKM %q (%q)\n", "bonding", args)
|
||||||
|
return exec.Command("modprobe", args...).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartNetworkd() error {
|
func restartNetworkd() error {
|
||||||
_, err := RunUnitCommand("restart", "systemd-networkd.service")
|
log.Printf("Restarting networkd.service\n")
|
||||||
|
_, err := NewUnitManager("").RunUnitCommand("restart", "systemd-networkd.service")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Filename()))
|
filename := fmt.Sprintf("%s.netdev", iface.Filename())
|
||||||
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Filename()))
|
filename = fmt.Sprintf("%s.link", iface.Filename())
|
||||||
if err := writeConfig(filename, iface.Link()); err != nil {
|
if err := writeConfig(filename, iface.Link()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Filename()))
|
filename = fmt.Sprintf("%s.network", iface.Filename())
|
||||||
if err := writeConfig(filename, iface.Network()); err != nil {
|
if err := writeConfig(filename, iface.Network()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -86,6 +113,7 @@ func writeConfig(filename string, config string) error {
|
|||||||
if config == "" {
|
if config == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
log.Printf("Writing networkd unit %q\n", filename)
|
||||||
return ioutil.WriteFile(filename, []byte(config), 0444)
|
_, err := WriteFile(&File{Content: config, Path: filename}, runtimeNetworkPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -13,63 +13,21 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/coreos/go-systemd/dbus"
|
"github.com/coreos/coreos-cloudinit/third_party/github.com/coreos/go-systemd/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewUnitManager(root string) UnitManager {
|
||||||
|
return &systemd{root}
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemd struct {
|
||||||
|
root string
|
||||||
|
}
|
||||||
|
|
||||||
// fakeMachineID is placed on non-usr CoreOS images and should
|
// fakeMachineID is placed on non-usr CoreOS images and should
|
||||||
// never be used as a true MachineID
|
// never be used as a true MachineID
|
||||||
const fakeMachineID = "42000000000000000000000000000042"
|
const fakeMachineID = "42000000000000000000000000000042"
|
||||||
|
|
||||||
// Name for drop-in service configuration files created by cloudconfig
|
|
||||||
const cloudConfigDropIn = "20-cloudinit.conf"
|
|
||||||
|
|
||||||
type Unit struct {
|
|
||||||
Name string
|
|
||||||
Mask bool
|
|
||||||
Enable bool
|
|
||||||
Runtime bool
|
|
||||||
Content string
|
|
||||||
Command string
|
|
||||||
|
|
||||||
// For drop-in units, a cloudinit.conf is generated.
|
|
||||||
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
|
||||||
// until the correct behaviour for multiple drop-in units is determined.
|
|
||||||
DropIn bool `yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Type() string {
|
|
||||||
ext := filepath.Ext(u.Name)
|
|
||||||
return strings.TrimLeft(ext, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Group() (group string) {
|
|
||||||
t := u.Type()
|
|
||||||
if t == "network" || t == "netdev" || t == "link" {
|
|
||||||
group = "network"
|
|
||||||
} else {
|
|
||||||
group = "system"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Script []byte
|
|
||||||
|
|
||||||
// Destination builds the appropriate absolute file path for
|
|
||||||
// the Unit. The root argument indicates the effective base
|
|
||||||
// directory of the system (similar to a chroot).
|
|
||||||
func (u *Unit) Destination(root string) string {
|
|
||||||
dir := "etc"
|
|
||||||
if u.Runtime {
|
|
||||||
dir = "run"
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.DropIn {
|
|
||||||
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
|
|
||||||
} else {
|
|
||||||
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlaceUnit writes a unit file at the provided destination, creating
|
// PlaceUnit writes a unit file at the provided destination, creating
|
||||||
// parent directories as necessary.
|
// parent directories as necessary.
|
||||||
func PlaceUnit(u *Unit, dst string) error {
|
func (s *systemd) PlaceUnit(u *Unit, dst string) error {
|
||||||
dir := filepath.Dir(dst)
|
dir := filepath.Dir(dst)
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||||
@@ -91,7 +49,7 @@ func PlaceUnit(u *Unit, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnableUnitFile(unit string, runtime bool) error {
|
func (s *systemd) EnableUnitFile(unit string, runtime bool) error {
|
||||||
conn, err := dbus.New()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -102,7 +60,7 @@ func EnableUnitFile(unit string, runtime bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunUnitCommand(command, unit string) (string, error) {
|
func (s *systemd) RunUnitCommand(command, unit string) (string, error) {
|
||||||
conn, err := dbus.New()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -131,7 +89,7 @@ func RunUnitCommand(command, unit string) (string, error) {
|
|||||||
return fn(unit, "replace")
|
return fn(unit, "replace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DaemonReload() error {
|
func (s *systemd) DaemonReload() error {
|
||||||
conn, err := dbus.New()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -140,6 +98,57 @@ func DaemonReload() error {
|
|||||||
return conn.Reload()
|
return conn.Reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaskUnit masks the given Unit by symlinking its unit file to
|
||||||
|
// /dev/null, analogous to `systemctl mask`.
|
||||||
|
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
||||||
|
// file at the location*, to ensure that the mask will succeed.
|
||||||
|
func (s *systemd) MaskUnit(unit *Unit) error {
|
||||||
|
masked := unit.Destination(s.root)
|
||||||
|
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := os.Remove(masked); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Symlink("/dev/null", masked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
|
||||||
|
// associated with the given Unit is empty or appears to be a symlink to
|
||||||
|
// /dev/null, it is removed.
|
||||||
|
func (s *systemd) UnmaskUnit(unit *Unit) error {
|
||||||
|
masked := unit.Destination(s.root)
|
||||||
|
ne, err := nullOrEmpty(masked)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ne {
|
||||||
|
log.Printf("%s is not null or empty, refusing to unmask", masked)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.Remove(masked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nullOrEmpty checks whether a given path appears to be an empty regular file
|
||||||
|
// or a symlink to /dev/null
|
||||||
|
func nullOrEmpty(path string) (bool, error) {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
m := fi.Mode()
|
||||||
|
if m.IsRegular() && fi.Size() <= 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if m&os.ModeCharDevice > 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ExecuteScript(scriptPath string) (string, error) {
|
func ExecuteScript(scriptPath string) (string, error) {
|
||||||
props := []dbus.Property{
|
props := []dbus.Property{
|
||||||
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
|
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
|
||||||
@@ -178,54 +187,3 @@ func MachineID(root string) string {
|
|||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaskUnit masks the given Unit by symlinking its unit file to
|
|
||||||
// /dev/null, analogous to `systemctl mask`.
|
|
||||||
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
|
||||||
// file at the location*, to ensure that the mask will succeed.
|
|
||||||
func MaskUnit(unit *Unit, root string) error {
|
|
||||||
masked := unit.Destination(root)
|
|
||||||
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err := os.Remove(masked); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Symlink("/dev/null", masked)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
|
|
||||||
// associated with the given Unit is empty or appears to be a symlink to
|
|
||||||
// /dev/null, it is removed.
|
|
||||||
func UnmaskUnit(unit *Unit, root string) error {
|
|
||||||
masked := unit.Destination(root)
|
|
||||||
ne, err := nullOrEmpty(masked)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ne {
|
|
||||||
log.Printf("%s is not null or empty, refusing to unmask", masked)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return os.Remove(masked)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nullOrEmpty checks whether a given path appears to be an empty regular file
|
|
||||||
// or a symlink to /dev/null
|
|
||||||
func nullOrEmpty(path string) (bool, error) {
|
|
||||||
fi, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
m := fi.Mode()
|
|
||||||
if m.IsRegular() && fi.Size() <= 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if m&os.ModeCharDevice > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
@@ -25,13 +25,15 @@ Address=10.209.171.177/19
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := &systemd{dir}
|
||||||
|
|
||||||
dst := u.Destination(dir)
|
dst := u.Destination(dir)
|
||||||
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
|
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
|
||||||
if dst != expectDst {
|
if dst != expectDst {
|
||||||
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := PlaceUnit(&u, dst); err != nil {
|
if err := sd.PlaceUnit(&u, dst); err != nil {
|
||||||
t.Fatalf("PlaceUnit failed: %v", err)
|
t.Fatalf("PlaceUnit failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +102,15 @@ Where=/media/state
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := &systemd{dir}
|
||||||
|
|
||||||
dst := u.Destination(dir)
|
dst := u.Destination(dir)
|
||||||
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
|
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
|
||||||
if dst != expectDst {
|
if dst != expectDst {
|
||||||
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := PlaceUnit(&u, dst); err != nil {
|
if err := sd.PlaceUnit(&u, dst); err != nil {
|
||||||
t.Fatalf("PlaceUnit failed: %v", err)
|
t.Fatalf("PlaceUnit failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +159,11 @@ func TestMaskUnit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := &systemd{dir}
|
||||||
|
|
||||||
// Ensure mask works with units that do not currently exist
|
// Ensure mask works with units that do not currently exist
|
||||||
uf := &Unit{Name: "foo.service"}
|
uf := &Unit{Name: "foo.service"}
|
||||||
if err := MaskUnit(uf, dir); err != nil {
|
if err := sd.MaskUnit(uf); err != nil {
|
||||||
t.Fatalf("Unable to mask new unit: %v", err)
|
t.Fatalf("Unable to mask new unit: %v", err)
|
||||||
}
|
}
|
||||||
fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
||||||
@@ -175,7 +181,7 @@ func TestMaskUnit(t *testing.T) {
|
|||||||
if _, err := os.Create(barPath); err != nil {
|
if _, err := os.Create(barPath); err != nil {
|
||||||
t.Fatalf("Error creating new unit file: %v", err)
|
t.Fatalf("Error creating new unit file: %v", err)
|
||||||
}
|
}
|
||||||
if err := MaskUnit(ub, dir); err != nil {
|
if err := sd.MaskUnit(ub); err != nil {
|
||||||
t.Fatalf("Unable to mask existing unit: %v", err)
|
t.Fatalf("Unable to mask existing unit: %v", err)
|
||||||
}
|
}
|
||||||
barTgt, err := os.Readlink(barPath)
|
barTgt, err := os.Readlink(barPath)
|
||||||
@@ -194,8 +200,10 @@ func TestUnmaskUnit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sd := &systemd{dir}
|
||||||
|
|
||||||
nilUnit := &Unit{Name: "null.service"}
|
nilUnit := &Unit{Name: "null.service"}
|
||||||
if err := UnmaskUnit(nilUnit, dir); err != nil {
|
if err := sd.UnmaskUnit(nilUnit); err != nil {
|
||||||
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
|
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +219,7 @@ func TestUnmaskUnit(t *testing.T) {
|
|||||||
if err := ioutil.WriteFile(dst, []byte(uf.Content), 700); err != nil {
|
if err := ioutil.WriteFile(dst, []byte(uf.Content), 700); err != nil {
|
||||||
t.Fatalf("Unable to write unit file: %v", err)
|
t.Fatalf("Unable to write unit file: %v", err)
|
||||||
}
|
}
|
||||||
if err := UnmaskUnit(uf, dir); err != nil {
|
if err := sd.UnmaskUnit(uf); err != nil {
|
||||||
t.Errorf("unmask of non-empty unit returned unexpected error: %v", err)
|
t.Errorf("unmask of non-empty unit returned unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
got, _ := ioutil.ReadFile(dst)
|
got, _ := ioutil.ReadFile(dst)
|
||||||
@@ -224,7 +232,7 @@ func TestUnmaskUnit(t *testing.T) {
|
|||||||
if err := os.Symlink("/dev/null", dst); err != nil {
|
if err := os.Symlink("/dev/null", dst); err != nil {
|
||||||
t.Fatalf("Unable to create masked unit: %v", err)
|
t.Fatalf("Unable to create masked unit: %v", err)
|
||||||
}
|
}
|
||||||
if err := UnmaskUnit(ub, dir); err != nil {
|
if err := sd.UnmaskUnit(ub); err != nil {
|
||||||
t.Errorf("unmask of unit returned unexpected error: %v", err)
|
t.Errorf("unmask of unit returned unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(dst); !os.IsNotExist(err) {
|
if _, err := os.Stat(dst); !os.IsNotExist(err) {
|
||||||
|
67
system/unit.go
Normal file
67
system/unit.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name for drop-in service configuration files created by cloudconfig
|
||||||
|
const cloudConfigDropIn = "20-cloudinit.conf"
|
||||||
|
|
||||||
|
type UnitManager interface {
|
||||||
|
PlaceUnit(unit *Unit, dst string) error
|
||||||
|
EnableUnitFile(unit string, runtime bool) error
|
||||||
|
RunUnitCommand(command, unit string) (string, error)
|
||||||
|
DaemonReload() error
|
||||||
|
MaskUnit(unit *Unit) error
|
||||||
|
UnmaskUnit(unit *Unit) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unit struct {
|
||||||
|
Name string
|
||||||
|
Mask bool
|
||||||
|
Enable bool
|
||||||
|
Runtime bool
|
||||||
|
Content string
|
||||||
|
Command string
|
||||||
|
|
||||||
|
// For drop-in units, a cloudinit.conf is generated.
|
||||||
|
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
||||||
|
// until the correct behaviour for multiple drop-in units is determined.
|
||||||
|
DropIn bool `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Type() string {
|
||||||
|
ext := filepath.Ext(u.Name)
|
||||||
|
return strings.TrimLeft(ext, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Group() (group string) {
|
||||||
|
t := u.Type()
|
||||||
|
if t == "network" || t == "netdev" || t == "link" {
|
||||||
|
group = "network"
|
||||||
|
} else {
|
||||||
|
group = "system"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script []byte
|
||||||
|
|
||||||
|
// Destination builds the appropriate absolute file path for
|
||||||
|
// the Unit. The root argument indicates the effective base
|
||||||
|
// directory of the system (similar to a chroot).
|
||||||
|
func (u *Unit) Destination(root string) string {
|
||||||
|
dir := "etc"
|
||||||
|
if u.Runtime {
|
||||||
|
dir = "run"
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.DropIn {
|
||||||
|
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
|
||||||
|
} else {
|
||||||
|
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user