asteris-llc/converge

View on GitHub
resource/systemd/unit/systemd_linux.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build linux

package unit

import (
    "fmt"

    log "github.com/Sirupsen/logrus"
    "github.com/coreos/go-systemd/dbus"
    "github.com/pkg/errors"
)

// LinuxExecutor provides a command executor for interacting with systemd on Linux
type LinuxExecutor struct {
    dbusConn SystemdConnection
}

// ListUnits will use dbus to get a list of all units
func (l LinuxExecutor) ListUnits() ([]*Unit, error) {
    var units []*Unit
    unitStatuses, err := l.dbusConn.ListUnits()

    if err != nil {
        return units, err
    }

    for _, status := range unitStatuses {
        unit, err := unitFromStatus(l.dbusConn, &status)
        if err != nil {
            return units, err
        }
        units = append(units, unit)
    }
    return units, nil
}

// QueryUnit will use dbus to get the unit status by name
func (l LinuxExecutor) QueryUnit(unitName string, verify bool) (*Unit, error) {
    units, err := l.ListUnits()
    var toReturn *Unit

    if err != nil {
        return nil, errors.Wrap(err, "Cannot query unit by name")
    }

    for _, u := range units {
        if u.Name == unitName {
            toReturn = u
        }
    }

    if toReturn == nil {
        if verify {
            return nil, fmt.Errorf("%s: no such unit known", unitName)
        }
        toReturn = &Unit{ActiveState: "unknown"}
    }
    return toReturn, nil
}

// StartUnit will use dbus to start a unit
func (l LinuxExecutor) StartUnit(u *Unit) error {
    return runDbusCommand(l.dbusConn.StartUnit, u.Name, "replace", "starting")
}

// StopUnit will use dbus to stop a unit
func (l LinuxExecutor) StopUnit(u *Unit) error {
    return runDbusCommand(l.dbusConn.StopUnit, u.Name, "replace", "stopping")
}

// RestartUnit will restart a unit
func (l LinuxExecutor) RestartUnit(u *Unit) error {
    return runDbusCommand(l.dbusConn.RestartUnit, u.Name, "replace", "restarting")
}

// ReloadUnit will use dbus to reload a unit
func (l LinuxExecutor) ReloadUnit(u *Unit) error {
    return runDbusCommand(l.dbusConn.ReloadUnit, u.Name, "replace", "reloading")
}

// SendSignal will send a signal
func (l LinuxExecutor) SendSignal(u *Unit, signal Signal) {
    l.dbusConn.KillUnit(u.Name, int32(signal))
}

func runDbusCommand(f func(string, string, chan<- string) (int, error), name, mode, operation string) error {
    ch := make(chan string)
    defer close(ch)
    _, err := f(name, mode, ch)
    if err != nil {
        log.WithFields(log.Fields{
            "operation": operation,
            "unit name": name,
            "mode":      mode,
        }).Errorf("operation returned an error: %v", err)
        return err
    }
    msg := <-ch
    switch msg {
    case "done":
        return nil
    case "canceled":
        return fmt.Errorf("operation was cancelled while %s: %s", operation, name)
    case "timeout":
        return fmt.Errorf("operation timed out while %s: %s", operation, name)
    case "failed":
        return fmt.Errorf("operation failed while %s: %s", operation, name)
    case "dependency":
        return fmt.Errorf("operation depends on a failed unit when %s: %s", operation, name)
    case "skipped":
        return nil
    }
    log.WithFields(log.Fields{
        "operation": operation,
        "unit name": name,
        "message":   msg,
    }).Errorf("systemd returned an unknown message")
    return fmt.Errorf("unknown systemd status: %s", msg)
}

func realExecutor() (SystemdExecutor, error) {
    conn, err := dbus.New()
    if err != nil {
        return nil, err
    }
    return LinuxExecutor{conn}, nil
}

// Close will close a connection
func (l LinuxExecutor) Close() {
    l.dbusConn.Close()
}

// NewSystemExecutor will generate a new real executor
func NewSystemExecutor() SystemdExecutor {
    executor, err := realExecutor()
    if err != nil {
        panic(err)
    }
    return executor
}

func unitFromStatus(conn SystemdConnection, status *dbus.UnitStatus) (*Unit, error) {
    u := newFromStatus(status)

    properties, err := conn.GetUnitProperties(status.Name)
    if err != nil {
        return nil, err
    }
    u.SetProperties(properties)

    if u.Type.HasProperties() {
        typeProperties, err := conn.GetUnitTypeProperties(status.Name, u.Type.UnitTypeString())
        if err != nil {
            return nil, err
        }
        u.SetTypedProperties(typeProperties)
    }

    return u, nil
}

func (l LinuxExecutor) unitExists(unitName string) (bool, error) {
    units, err := l.ListUnits()
    if err != nil {
        return false, err
    }
    for _, u := range units {
        if u.Name == unitName {
            return true, nil
        }
    }
    return false, nil
}