Compare commits

...

16 Commits

Author SHA1 Message Date
Brian Waldon
639c693153 chore(release): Bump version to v0.3.2 2014-03-27 18:14:16 -07:00
Brian Waldon
b4027077ff Merge pull request #55 from bcwaldon/drop-reload
fix(units): Drop automatic daemon-reload
2014-03-27 18:12:22 -07:00
Brian Waldon
580460ff3f fix(units): Drop automatic daemon-reload 2014-03-27 17:30:05 -07:00
Brian Waldon
b246ec0397 chore(release): Bump version to v0.3.1+git 2014-03-25 20:06:19 -07:00
Brian Waldon
4977c774d8 chore(release): Bump version to v0.3.1 2014-03-25 20:06:07 -07:00
Brian Waldon
661bae11fc Merge pull request #53 from bcwaldon/fix-reload
Fix systemd daemon-reload
2014-03-25 20:04:24 -07:00
Brian Waldon
58ae898948 fix(systemd): Update usage of dbus.Reload 2014-03-25 19:37:05 -07:00
Brian Waldon
f5f9a0a6a9 bump(github.com/coreos/go-systemd/dbus): 4fbc5060a317b142e6c7bfbedb65596d5f0ab99b 2014-03-25 19:37:05 -07:00
Brian Waldon
477ae29135 fix(systemd): Fail if daemon-reload returns error 2014-03-25 18:50:48 -07:00
Brian Waldon
0203d4a9f3 chore(release): Bump version to v0.3.0+git 2014-03-24 18:03:45 -07:00
Brian Waldon
e68134d884 chore(release): Bump version to v0.3.0 2014-03-24 18:03:34 -07:00
Brian Waldon
2ad33487d7 Merge pull request #51 from bcwaldon/default-command
fix(unit): Default unit command to NOP
2014-03-24 16:29:58 -07:00
Brian Waldon
b778fe6f41 fix(unit): Default unit command to NOP 2014-03-24 14:12:59 -07:00
Brian Waldon
3d7bda9f6b Merge pull request #49 from bcwaldon/oem-release
oem-release
2014-03-24 13:43:10 -07:00
Brian Waldon
3d01211937 feat(coreos.oem): Write coreos.oem fields to /etc/oem-release 2014-03-24 13:42:35 -07:00
Brian Waldon
61808c2002 chore(release): Bump version to v0.2.2+git 2014-03-21 14:43:16 -07:00
11 changed files with 376 additions and 20 deletions

View File

@@ -39,6 +39,45 @@ 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.oem
These fields are borrowed from the [os-release spec][os-release] and repurposed
as a way for coreos-cloudinit to know about the OEM partition on this machine:
- **id**: Lowercase string identifying the OEM
- **name**: Human-friendly string representing the OEM
- **version-id**: Lowercase string identifying the version of the OEM
- **home-url**: Link to the homepage of the provider or OEM
- **bug-report-url**: Link to a place to file bug reports about this OEM
coreos-cloudinit renders these fields to `/etc/oem-release`.
If no **id** field is provided, coreos-cloudinit will ignore this section.
For example, the following cloud-config document...
```
#cloud-config
coreos:
oem:
id: rackspace
name: Rackspace Cloud Servers
version-id: 168.0.0
home-url: https://www.rackspace.com/cloud/servers/
bug-report-url: https://github.com/coreos/coreos-overlay
```
...would be rendered to the following `/etc/oem-release`:
```
ID="rackspace"
NAME="Rackspace Cloud Servers"
VERSION_ID="168.0.0"
HOME_URL="https://www.rackspace.com/cloud/servers/"
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
```
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
### coreos.units
Arbitrary systemd units may be provided in the `coreos.units` attribute.

View File

@@ -12,7 +12,7 @@ import (
"github.com/coreos/coreos-cloudinit/system"
)
const version = "0.2.2"
const version = "0.3.2"
func main() {
var printVersion bool

View File

@@ -15,6 +15,7 @@ type CloudConfig struct {
Coreos struct {
Etcd EtcdEnvironment
Units []system.Unit
OEM OEMRelease
}
WriteFiles []system.File `yaml:"write_files"`
Hostname string
@@ -47,6 +48,13 @@ func Apply(cfg CloudConfig, env *Environment) error {
log.Printf("Set hostname to %s", cfg.Hostname)
}
if cfg.Coreos.OEM.ID != "" {
if err := WriteOEMRelease(&cfg.Coreos.OEM, env.Root()); err != nil {
return err
}
log.Printf("Wrote /etc/oem-release to filesystem")
}
if len(cfg.Users) > 0 {
for _, user := range cfg.Users {
if user.Name == "" {
@@ -136,18 +144,14 @@ func Apply(cfg CloudConfig, env *Environment) error {
}
}
if unit.Group() != "network" {
command := unit.Command
if command == "" {
command = "restart"
}
commands[unit.Name] = command
} else {
if unit.Group() == "network" {
commands["systemd-networkd.service"] = "restart"
} else {
if unit.Command != "" {
commands[unit.Name] = unit.Command
}
}
}
system.DaemonReload()
for unit, command := range commands {
log.Printf("Calling unit command '%s %s'", command, unit)

View File

@@ -45,6 +45,12 @@ coreos:
Address=10.209.171.177/19
'
oem:
id: rackspace
name: Rackspace Cloud Servers
version-id: 168.0.0
home-url: https://www.rackspace.com/cloud/servers/
bug-report-url: https://github.com/coreos/coreos-overlay
ssh_authorized_keys:
- foobar
- foobaz
@@ -116,6 +122,10 @@ Address=10.209.171.177/19
}
}
if cfg.Coreos.OEM.ID != "rackspace" {
t.Errorf("Failed parsing coreos.oem. Expected ID 'rackspace', got %q.", cfg.Coreos.OEM.ID)
}
if cfg.Hostname != "trontastic" {
t.Errorf("Failed to parse hostname")
}

39
initialize/oem.go Normal file
View File

@@ -0,0 +1,39 @@
package initialize
import (
"fmt"
"path"
"strings"
"github.com/coreos/coreos-cloudinit/system"
)
type OEMRelease struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
VersionID string `yaml:"version-id"`
HomeURL string `yaml:"home-url"`
BugReportURL string `yaml:"bug-report-url"`
}
func (oem *OEMRelease) String() string {
fields := []string{
fmt.Sprintf("ID=%q", oem.ID),
fmt.Sprintf("NAME=%q", oem.Name),
fmt.Sprintf("VERSION_ID=%q", oem.VersionID),
fmt.Sprintf("HOME_URL=%q", oem.HomeURL),
fmt.Sprintf("BUG_REPORT_URL=%q", oem.BugReportURL),
}
return strings.Join(fields, "\n") + "\n"
}
func WriteOEMRelease(oem *OEMRelease, root string) error {
file := system.File{
Path: path.Join(root, "etc", "oem-release"),
RawFilePermissions: "0644",
Content: oem.String(),
}
return system.WriteFile(&file)
}

54
initialize/oem_test.go Normal file
View File

@@ -0,0 +1,54 @@
package initialize
import (
"io/ioutil"
"os"
"path"
"syscall"
"testing"
)
func TestOEMReleaseWrittenToDisk(t *testing.T) {
oem := OEMRelease{
ID: "rackspace",
Name: "Rackspace Cloud Servers",
VersionID: "168.0.0",
HomeURL: "https://www.rackspace.com/cloud/servers/",
BugReportURL: "https://github.com/coreos/coreos-overlay",
}
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 := WriteOEMRelease(&oem, dir); err != nil {
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
}
fullPath := path.Join(dir, "etc", "oem-release")
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 := `ID="rackspace"
NAME="Rackspace Cloud Servers"
VERSION_ID="168.0.0"
HOME_URL="https://www.rackspace.com/cloud/servers/"
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
`
if string(contents) != expect {
t.Fatalf("File has incorrect contents")
}
}

View File

@@ -116,8 +116,7 @@ func DaemonReload() error {
return err
}
_, err = conn.Reload()
return err
return conn.Reload()
}
func ExecuteScript(scriptPath string) (string, error) {

View File

@@ -18,6 +18,8 @@ limitations under the License.
package dbus
import (
"os"
"strconv"
"strings"
"sync"
@@ -73,7 +75,12 @@ func (c *Conn) initConnection() error {
return err
}
err = c.sysconn.Auth(nil)
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.sysconn.Auth(methods)
if err != nil {
c.sysconn.Close()
return err

View File

@@ -35,6 +35,7 @@ func (c *Conn) jobComplete(signal *dbus.Signal) {
out, ok := c.jobListener.jobs[job]
if ok {
out <- result
delete(c.jobListener.jobs, job)
}
c.jobListener.Unlock()
}
@@ -137,8 +138,8 @@ func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
}
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
var err error
var props map[string]dbus.Variant
@@ -148,7 +149,7 @@ func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "org.freedesktop.systemd1.Unit").Store(&props)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
if err != nil {
return nil, err
}
@@ -161,6 +162,55 @@ func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
return out, nil
}
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
}
func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
var err error
var prop dbus.Variant
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: propertyName, Value: prop}, nil
}
func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
}
// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
}
// SetUnitProperties() may be used to modify certain unit properties at runtime.
// Not all properties may be changed at runtime, but many resource management
// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
// instantly, and stored on disk for future boots, unless runtime is true, in which
// case the settings only apply until the next reboot. name is the name of the unit
// to modify. properties are the settings to set, encoded as an array of property
// name and value pairs.
func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store()
}
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName)
}
// ListUnits returns an array with all currently loaded units. Note that
// units may be known by multiple names at the same time, and hence there might
// be more unit names loaded than actual units behind them.
@@ -253,8 +303,52 @@ type EnableUnitFileChange struct {
Destination string // Destination of the symlink
}
// DisableUnitFiles() may be used to disable one or more units in the system (by
// removing symlinks to them from /etc or /run).
//
// It takes a list of unit files to disable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and one boolean: whether the unit was enabled for runtime
// only (true, /run), or persistently (false, /etc).
//
// This call returns an array with the changes made. The changes list
// consists of structures with three strings: the type of the change (one of
// symlink or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]DisableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type DisableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// Reload instructs systemd to scan for and reload unit files. This is
// equivalent to a 'systemctl daemon-reload'.
func (c *Conn) Reload() (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.Reload")
func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
}

View File

@@ -18,9 +18,11 @@ package dbus
import (
"fmt"
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
"math/rand"
"os"
"path/filepath"
"reflect"
"testing"
)
@@ -50,13 +52,16 @@ func setupUnit(target string, conn *Conn, t *testing.T) {
fixture := []string{abs}
install, changes, err := conn.EnableUnitFiles(fixture, true, true)
if err != nil {
t.Fatal(err)
}
if install != false {
t.Fatal("Install was true")
}
if len(changes) < 1 {
t.Fatal("Expected one change, got %v", changes)
t.Fatalf("Expected one change, got %v", changes)
}
if changes[0].Filename != targetRun {
@@ -118,6 +123,37 @@ func TestStartStopUnit(t *testing.T) {
}
}
// Enables a unit and then immediately tears it down
func TestEnableDisableUnit(t *testing.T) {
target := "enable-disable.service"
conn := setupConn(t)
setupUnit(target, conn, t)
abs, err := filepath.Abs("../fixtures/" + target)
if err != nil {
t.Fatal(err)
}
path := filepath.Join("/run/systemd/system/", target)
// 2. Disable the unit
changes, err := conn.DisableUnitFiles([]string{abs}, true)
if err != nil {
t.Fatal(err)
}
if len(changes) != 1 {
t.Fatalf("Changes should include the path, %v", changes)
}
if changes[0].Filename != path {
t.Fatalf("Change should include correct filename, %+v", changes[0])
}
if changes[0].Destination != "" {
t.Fatalf("Change destination should be empty, %+v", changes[0])
}
}
// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
// systems and ensures that one of its properties is valid.
func TestGetUnitProperties(t *testing.T) {
@@ -139,6 +175,20 @@ func TestGetUnitProperties(t *testing.T) {
if names[0] != "system.slice" {
t.Fatal("unexpected wants for /")
}
prop, err := conn.GetUnitProperty(unit, "Wants")
if err != nil {
t.Fatal(err)
}
if prop.Name != "Wants" {
t.Fatal("unexpected property name")
}
val := prop.Value.Value().([]string)
if !reflect.DeepEqual(val, names) {
t.Fatal("unexpected property value")
}
}
// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a
@@ -150,10 +200,37 @@ func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
unit := "//invalid#$^/"
_, err := conn.GetUnitProperties(unit)
if err == nil {
t.Fatal("Expected an error, got nil")
}
_, err = conn.GetUnitProperty(unit, "Wants")
if err == nil {
t.Fatal("Expected an error, got nil")
}
}
// TestSetUnitProperties changes a cgroup setting on the `tmp.mount`
// which should exist on all systemd systems and ensures that the
// property was set.
func TestSetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "tmp.mount"
if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil {
t.Fatal(err)
}
info, err := conn.GetUnitTypeProperties(unit, "Mount")
if err != nil {
t.Fatal(err)
}
value := info["CPUShares"].(uint64)
if value != 1023 {
t.Fatal("CPUShares of unit is not 1023, %s", value)
}
}
// Ensure that basic transient unit starting and stopping works.
@@ -211,3 +288,27 @@ func TestStartStopTransientUnit(t *testing.T) {
t.Fatalf("Test unit found in list, should be stopped")
}
}
func TestConnJobListener(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
setupUnit(target, conn, t)
jobSize := len(conn.jobListener.jobs)
_, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
_, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
currentJobSize := len(conn.jobListener.jobs)
if jobSize != currentJobSize {
t.Fatal("JobListener jobs leaked")
}
}

View File

@@ -209,3 +209,12 @@ func PropPropagatesReloadTo(units ...string) Property {
func PropRequiresMountsFor(units ...string) Property {
return propDependency("RequiresMountsFor", units)
}
// PropSlice sets the Slice unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
func PropSlice(slice string) Property {
return Property{
Name: "Slice",
Value: dbus.MakeVariant(slice),
}
}