Example #1
0
func (n Nginx) Reload() error {
	n.Lock()
	defer n.Unlock()

	switch cfg.Current.Nginx.Mode {
	case "systemd":
		if n.Systemd == nil {
			c, err := dbus.NewSystemdConnection()
			if err != nil {
				return err
			}
			n.Systemd = c
		}

		ch := make(chan string)
		_, err := n.Systemd.ReloadUnit(cfg.Current.Nginx.Systemd.Service, "replace", ch)
		if err != nil {
			return err
		}
		<-ch

		return nil

	default:
		return errors.New("unknown Nginx mode")
	}

	panic("unreachable")
}
Example #2
0
File: service.go Project: 40a/mgmt
func (obj *ServiceType) Apply() bool {
	log.Printf("%v[%v]: Apply", obj.GetType(), obj.GetName())

	if !util.IsRunningSystemd() {
		log.Fatal("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		log.Fatal("Failed to connect to systemd: ", err)
	}
	defer conn.Close()

	var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
	var files = []string{service}                     // the service represented in a list
	if obj.Startup == "enabled" {
		_, _, err = conn.EnableUnitFiles(files, false, true)

	} else if obj.Startup == "disabled" {
		_, err = conn.DisableUnitFiles(files, false)
	} else {
		err = nil
	}
	if err != nil {
		log.Printf("Unable to change startup status: %v", err)
		return false
	}

	result := make(chan string, 1) // catch result information

	if obj.State == "running" {
		_, err := conn.StartUnit(service, "fail", result)
		if err != nil {
			log.Fatal("Failed to start unit: ", err)
			return false
		}
	} else if obj.State == "stopped" {
		_, err = conn.StopUnit(service, "fail", result)
		if err != nil {
			log.Fatal("Failed to stop unit: ", err)
			return false
		}
	} else {
		log.Fatal("Unknown state: ", obj.State)
	}

	status := <-result
	if &status == nil {
		log.Fatal("Result is nil")
		return false
	}
	if status != "done" {
		log.Fatal("Unknown return string: ", status)
		return false
	}

	// XXX: also set enabled on boot

	return true
}
Example #3
0
// WaitOnDevices waits for the devices named in devs to be plugged before returning.
func WaitOnDevices(devs []string, stage string) error {
	conn, err := dbus.NewSystemdConnection()
	if err != nil {
		return err
	}

	results := map[string]chan string{}
	for _, dev := range devs {
		unitName := unit.UnitNamePathEscape(dev + ".device")
		results[unitName] = make(chan string)

		if _, err = conn.StartUnit(unitName, "replace", results[unitName]); err != nil {
			return fmt.Errorf("failed starting device unit %s: %v", unitName, err)
		}
	}

	for unitName, result := range results {
		s := <-result

		if s != "done" {
			return fmt.Errorf("device unit %s %s", unitName, s)
		}
	}

	return nil
}
Example #4
0
// Reload tells haproxy to reload its configuration
func Reload() (err error) {
	conn, err := dbus.NewSystemdConnection()
	if err != nil {
		return err
	}

	_, err = conn.RestartUnit("haproxy.cycore@"+instanceID+".service", "ignore-dependencies", nil)

	return err
}
Example #5
0
File: service.go Project: 40a/mgmt
func (obj *ServiceType) StateOK() bool {
	if obj.isStateOK { // cache the state
		return true
	}

	if !util.IsRunningSystemd() {
		log.Fatal("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		log.Fatal("Failed to connect to systemd: ", err)
	}
	defer conn.Close()

	var service = fmt.Sprintf("%v.service", obj.Name) // systemd name

	loadstate, err := conn.GetUnitProperty(service, "LoadState")
	if err != nil {
		log.Printf("Failed to get load state: %v", err)
		return false
	}

	// NOTE: we have to compare variants with other variants, they are really strings...
	var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
	if notFound {
		log.Printf("Failed to find service: %v", service)
		return false
	}

	// XXX: check service "enabled at boot" or not status...

	//conn.GetUnitProperties(service)
	activestate, err := conn.GetUnitProperty(service, "ActiveState")
	if err != nil {
		log.Fatal("Failed to get active state: ", err)
	}

	var running = (activestate.Value == dbus.MakeVariant("active"))

	if obj.State == "running" {
		if !running {
			return false // we are in the wrong state
		}
	} else if obj.State == "stopped" {
		if running {
			return false
		}
	} else {
		log.Fatal("Unknown state: ", obj.State)
	}

	return true // all is good, no state change needed
}
Example #6
0
func (s *ServiceStatus) Init() error {
	var err error
	if s.Name == "" {
		return e.New(ErrInvServName)
	}
	s.conn, err = sd.NewSystemdConnection()
	if err != nil {
		return e.Forward(err)
	}
	err = s.conn.Subscribe()
	if err != nil {
		return e.Forward(err)
	}
	ch, cherr := s.conn.SubscribeUnits(s.Interval)
	go func() {
		for {
			select {
			case status := <-ch:
				if status == nil {
					continue
				}
				st, found := status[s.Name]
				if !found {
					log.Tag("systemd").Printf("Status of %v is unknow.", s.Name)
					s.setStatus(Unknow)
					continue
				}
				if s == nil {
					s.setStatus(Deleted)
					continue
				}
				if st.LoadState == "loaded" && st.ActiveState == "active" && st.SubState == "running" {
					s.setStatus(Running)
				} else if st.LoadState == "loaded" && st.ActiveState == "active" && st.SubState == "active" {
					s.setStatus(Running)
				} else if st.LoadState == "not-found" && st.ActiveState == "active" && st.SubState == "exited" {
					s.setStatus(Stopped)
				} else if st.LoadState == "loaded" && st.ActiveState == "inactive" && st.SubState == "dead" {
					s.setStatus(Stopped)
				} else {
					//ActiveState: deactivating, LoadState: loaded, SubState: stop-sigterm
					log.Tag("systemd").Printf("ActiveState: %v, LoadState: %v, SubState: %v", st.ActiveState, st.LoadState, st.SubState)
					s.setStatus(Other)
				}
			case err := <-cherr:
				log.Tag("systemd", "service", "status").Fatalln("SubscribeUnits error:", err)
			}
		}
	}()
	return nil
}
Example #7
0
// Watch is the primary listener for this resource and it outputs events.
func (obj *SvcRes) Watch(processChan chan event.Event) error {
	if obj.IsWatching() {
		return nil
	}
	obj.SetWatching(true)
	defer obj.SetWatching(false)
	cuid := obj.converger.Register()
	defer cuid.Unregister()

	var startup bool
	Startup := func(block bool) <-chan time.Time {
		if block {
			return nil // blocks forever
			//return make(chan time.Time) // blocks forever
		}
		return time.After(time.Duration(500) * time.Millisecond) // 1/2 the resolution of converged timeout
	}

	// obj.Name: svc name
	if !systemdUtil.IsRunningSystemd() {
		return fmt.Errorf("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		return errwrap.Wrapf(err, "Failed to connect to systemd")
	}
	defer conn.Close()

	// if we share the bus with others, we will get each others messages!!
	bus, err := util.SystemBusPrivateUsable() // don't share the bus connection!
	if err != nil {
		return errwrap.Wrapf(err, "Failed to connect to bus")
	}

	// XXX: will this detect new units?
	bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
		"type='signal',interface='org.freedesktop.systemd1.Manager',member='Reloading'")
	buschan := make(chan *dbus.Signal, 10)
	bus.Signal(buschan)

	var svc = fmt.Sprintf("%s.service", obj.Name) // systemd name
	var send = false                              // send event?
	var exit = false
	var invalid = false              // does the svc exist or not?
	var previous bool                // previous invalid value
	set := conn.NewSubscriptionSet() // no error should be returned
	subChannel, subErrors := set.Subscribe()
	var activeSet = false

	for {
		// XXX: watch for an event for new units...
		// XXX: detect if startup enabled/disabled value changes...

		previous = invalid
		invalid = false

		// firstly, does svc even exist or not?
		loadstate, err := conn.GetUnitProperty(svc, "LoadState")
		if err != nil {
			log.Printf("Failed to get property: %v", err)
			invalid = true
		}

		if !invalid {
			var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
			if notFound { // XXX: in the loop we'll handle changes better...
				log.Printf("Failed to find svc: %s", svc)
				invalid = true // XXX: ?
			}
		}

		if previous != invalid { // if invalid changed, send signal
			send = true
			obj.StateOK(false) // dirty
		}

		if invalid {
			log.Printf("Waiting for: %s", svc) // waiting for svc to appear...
			if activeSet {
				activeSet = false
				set.Remove(svc) // no return value should ever occur
			}

			obj.SetState(ResStateWatching) // reset
			select {
			case <-buschan: // XXX: wait for new units event to unstick
				cuid.SetConverged(false)
				// loop so that we can see the changed invalid signal
				log.Printf("Svc[%s]->DaemonReload()", svc)

			case event := <-obj.Events():
				cuid.SetConverged(false)
				if exit, send = obj.ReadEvent(&event); exit {
					return nil // exit
				}

			case <-cuid.ConvergedTimer():
				cuid.SetConverged(true) // converged!
				continue

			case <-Startup(startup):
				cuid.SetConverged(false)
				send = true
				obj.StateOK(false) // dirty
			}
		} else {
			if !activeSet {
				activeSet = true
				set.Add(svc) // no return value should ever occur
			}

			log.Printf("Watching: %s", svc) // attempting to watch...
			obj.SetState(ResStateWatching)  // reset
			select {
			case event := <-subChannel:

				log.Printf("Svc event: %+v", event)
				// NOTE: the value returned is a map for some reason...
				if event[svc] != nil {
					// event[svc].ActiveState is not nil

					switch event[svc].ActiveState {
					case "active":
						log.Printf("Svc[%s]->Started", svc)
					case "inactive":
						log.Printf("Svc[%s]->Stopped", svc)
					case "reloading":
						log.Printf("Svc[%s]->Reloading", svc)
					default:
						log.Fatalf("Unknown svc state: %s", event[svc].ActiveState)
					}
				} else {
					// svc stopped (and ActiveState is nil...)
					log.Printf("Svc[%s]->Stopped", svc)
				}
				send = true
				obj.StateOK(false) // dirty

			case err := <-subErrors:
				cuid.SetConverged(false)
				return errwrap.Wrapf(err, "Unknown %s[%s] error", obj.Kind(), obj.GetName())

			case event := <-obj.Events():
				cuid.SetConverged(false)
				if exit, send = obj.ReadEvent(&event); exit {
					return nil // exit
				}

			case <-cuid.ConvergedTimer():
				cuid.SetConverged(true) // converged!
				continue

			case <-Startup(startup):
				cuid.SetConverged(false)
				send = true
				obj.StateOK(false) // dirty
			}
		}

		if send {
			startup = true // startup finished
			send = false
			if exit, err := obj.DoSend(processChan, ""); exit || err != nil {
				return err // we exit or bubble up a NACK...
			}
		}
	}
}
Example #8
0
// CheckApply checks the resource state and applies the resource if the bool
// input is true. It returns error info and if the state check passed or not.
func (obj *SvcRes) CheckApply(apply bool) (checkOK bool, err error) {
	if !systemdUtil.IsRunningSystemd() {
		return false, fmt.Errorf("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		return false, errwrap.Wrapf(err, "Failed to connect to systemd")
	}
	defer conn.Close()

	var svc = fmt.Sprintf("%s.service", obj.Name) // systemd name

	loadstate, err := conn.GetUnitProperty(svc, "LoadState")
	if err != nil {
		return false, errwrap.Wrapf(err, "Failed to get load state")
	}

	// NOTE: we have to compare variants with other variants, they are really strings...
	var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
	if notFound {
		return false, errwrap.Wrapf(err, "Failed to find svc: %s", svc)
	}

	// XXX: check svc "enabled at boot" or not status...

	//conn.GetUnitProperties(svc)
	activestate, err := conn.GetUnitProperty(svc, "ActiveState")
	if err != nil {
		return false, errwrap.Wrapf(err, "Failed to get active state")
	}

	var running = (activestate.Value == dbus.MakeVariant("active"))
	var stateOK = ((obj.State == "") || (obj.State == "running" && running) || (obj.State == "stopped" && !running))
	var startupOK = true        // XXX: DETECT AND SET
	var refresh = obj.Refresh() // do we have a pending reload to apply?

	if stateOK && startupOK && !refresh {
		return true, nil // we are in the correct state
	}

	// state is not okay, no work done, exit, but without error
	if !apply {
		return false, nil
	}

	// apply portion
	log.Printf("%s[%s]: Apply", obj.Kind(), obj.GetName())
	var files = []string{svc} // the svc represented in a list
	if obj.Startup == "enabled" {
		_, _, err = conn.EnableUnitFiles(files, false, true)

	} else if obj.Startup == "disabled" {
		_, err = conn.DisableUnitFiles(files, false)
	}

	if err != nil {
		return false, errwrap.Wrapf(err, "Unable to change startup status")
	}

	// XXX: do we need to use a buffered channel here?
	result := make(chan string, 1) // catch result information

	if obj.State == "running" {
		_, err = conn.StartUnit(svc, "fail", result)
		if err != nil {
			return false, errwrap.Wrapf(err, "Failed to start unit")
		}
		if refresh {
			log.Printf("%s[%s]: Skipping reload, due to pending start", obj.Kind(), obj.GetName())
		}
		refresh = false // we did a start, so a reload is not needed
	} else if obj.State == "stopped" {
		_, err = conn.StopUnit(svc, "fail", result)
		if err != nil {
			return false, errwrap.Wrapf(err, "Failed to stop unit")
		}
		if refresh {
			log.Printf("%s[%s]: Skipping reload, due to pending stop", obj.Kind(), obj.GetName())
		}
		refresh = false // we did a stop, so a reload is not needed
	}

	status := <-result
	if &status == nil {
		return false, fmt.Errorf("Systemd service action result is nil")
	}
	if status != "done" {
		return false, fmt.Errorf("Unknown systemd return string: %v", status)
	}

	if refresh { // we need to reload the service
		// XXX: run a svc reload here!
		log.Printf("%s[%s]: Reloading...", obj.Kind(), obj.GetName())
	}

	// XXX: also set enabled on boot

	return false, nil // success
}
Example #9
0
File: service.go Project: 40a/mgmt
// Service watcher
func (obj *ServiceType) Watch() {
	if obj.IsWatching() {
		return
	}
	obj.SetWatching(true)
	defer obj.SetWatching(false)

	// obj.Name: service name
	//vertex := obj.GetVertex()         // stored with SetVertex
	if !util.IsRunningSystemd() {
		log.Fatal("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		log.Fatal("Failed to connect to systemd: ", err)
	}
	defer conn.Close()

	bus, err := dbus.SystemBus()
	if err != nil {
		log.Fatal("Failed to connect to bus: ", err)
	}

	// XXX: will this detect new units?
	bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
		"type='signal',interface='org.freedesktop.systemd1.Manager',member='Reloading'")
	buschan := make(chan *dbus.Signal, 10)
	bus.Signal(buschan)

	var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
	var send = false                                  // send event?
	var dirty = false
	var invalid = false              // does the service exist or not?
	var previous bool                // previous invalid value
	set := conn.NewSubscriptionSet() // no error should be returned
	subChannel, subErrors := set.Subscribe()
	var activeSet = false

	for {
		// XXX: watch for an event for new units...
		// XXX: detect if startup enabled/disabled value changes...

		previous = invalid
		invalid = false

		// firstly, does service even exist or not?
		loadstate, err := conn.GetUnitProperty(service, "LoadState")
		if err != nil {
			log.Printf("Failed to get property: %v", err)
			invalid = true
		}

		if !invalid {
			var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
			if notFound { // XXX: in the loop we'll handle changes better...
				log.Printf("Failed to find service: %v", service)
				invalid = true // XXX ?
			}
		}

		if previous != invalid { // if invalid changed, send signal
			send = true
			dirty = true
		}

		if invalid {
			log.Printf("Waiting for: %v", service) // waiting for service to appear...
			if activeSet {
				activeSet = false
				set.Remove(service) // no return value should ever occur
			}

			obj.SetState(typeWatching) // reset
			select {
			case _ = <-buschan: // XXX wait for new units event to unstick
				obj.SetConvergedState(typeConvergedNil)
				// loop so that we can see the changed invalid signal
				log.Printf("Service[%v]->DaemonReload()", service)

			case event := <-obj.events:
				obj.SetConvergedState(typeConvergedNil)
				if ok := obj.ReadEvent(&event); !ok {
					return // exit
				}
				if event.GetActivity() {
					dirty = true
				}
				send = true
			case _ = <-TimeAfterOrBlock(obj.ctimeout):
				obj.SetConvergedState(typeConvergedTimeout)
				obj.converged <- true
				continue
			}
		} else {
			if !activeSet {
				activeSet = true
				set.Add(service) // no return value should ever occur
			}

			log.Printf("Watching: %v", service) // attempting to watch...
			obj.SetState(typeWatching)          // reset
			select {
			case event := <-subChannel:

				log.Printf("Service event: %+v", event)
				// NOTE: the value returned is a map for some reason...
				if event[service] != nil {
					// event[service].ActiveState is not nil
					if event[service].ActiveState == "active" {
						log.Printf("Service[%v]->Started()", service)
					} else if event[service].ActiveState == "inactive" {
						log.Printf("Service[%v]->Stopped!()", service)
					} else {
						log.Fatal("Unknown service state: ", event[service].ActiveState)
					}
				} else {
					// service stopped (and ActiveState is nil...)
					log.Printf("Service[%v]->Stopped", service)
				}
				send = true
				dirty = true

			case err := <-subErrors:
				obj.SetConvergedState(typeConvergedNil) // XXX ?
				log.Println("error:", err)
				log.Fatal(err)
				//vertex.events <- fmt.Sprintf("service: %v", "error") // XXX: how should we handle errors?

			case event := <-obj.events:
				obj.SetConvergedState(typeConvergedNil)
				if ok := obj.ReadEvent(&event); !ok {
					return // exit
				}
				if event.GetActivity() {
					dirty = true
				}
				send = true
			}
		}

		if send {
			send = false
			if dirty {
				dirty = false
				obj.isStateOK = false // something made state dirty
			}
			Process(obj) // XXX: rename this function
		}

	}
}
func (c *systemdCollector) newDbus() (*dbus.Conn, error) {
	if *systemdPrivate {
		return dbus.NewSystemdConnection()
	}
	return dbus.New()
}
Example #11
0
func New(cfg Config) (*Collector, error) {
	var conn *dbus.Conn
	var err error
	switch cfg.ConnType {
	case ConnTypeDefault, ConnTypeDbus:
		conn, err = dbus.New()
	case ConnTypeDirect:
		conn, err = dbus.NewSystemdConnection()
	default:
		return nil, fmt.Errorf("unhandled ConnType: %v", cfg.ConnType)
	}
	if err != nil {
		return nil, fmt.Errorf("could not connect to systemd: %v", err)
	}

	c := &Collector{
		conn:  conn,
		units: make(map[string]*unitMetrics),
	}
	if cfg.DisableLoaded {
		c.loaded = nullmetric.NoopGaugeVec{}
	} else {
		c.loaded = c.metrics.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace:   "systemd",
				Subsystem:   "units",
				Name:        "loaded",
				Help:        "1 if the unit is loaded, 0 otherwise.",
				ConstLabels: cfg.ConstLabels,
			},
			[]string{"unit"},
		)
	}
	if cfg.DisableActive {
		c.active = nullmetric.NoopGaugeVec{}
	} else {
		c.active = c.metrics.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace:   "systemd",
				Subsystem:   "units",
				Name:        "active",
				Help:        "1 if the unit is active, 0 otherwise.",
				ConstLabels: cfg.ConstLabels,
			},
			[]string{"unit"},
		)
	}
	if cfg.DisableFailed {
		c.failed = nullmetric.NoopGaugeVec{}
	} else {
		c.failed = c.metrics.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace:   "systemd",
				Subsystem:   "units",
				Name:        "failed",
				Help:        "1 if the unit has failed, 0 otherwise.",
				ConstLabels: cfg.ConstLabels,
			},
			[]string{"unit"},
		)
	}
	if len(c.metrics) == 0 {
		return nil, fmt.Errorf("cannot disable all systemd metrics - remove [systemd] configuration instead")
	}
	return c, nil
}
Example #12
0
// Service watcher
func (obj ServiceType) Watch(v *Vertex) {
	// obj.Name: service name

	if !util.IsRunningSystemd() {
		log.Fatal("Systemd is not running.")
	}

	conn, err := systemd.NewSystemdConnection() // needs root access
	if err != nil {
		log.Fatal("Failed to connect to systemd: ", err)
	}
	defer conn.Close()

	bus, err := dbus.SystemBus()
	if err != nil {
		log.Fatal("Failed to connect to bus: %v\n", err)
	}

	// XXX: will this detect new units?
	bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
		"type='signal',interface='org.freedesktop.systemd1.Manager',member='Reloading'")
	buschan := make(chan *dbus.Signal, 10)
	bus.Signal(buschan)

	var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
	var send = false                                  // send event?
	var invalid = false                               // does the service exist or not?
	var previous bool                                 // previous invalid value
	set := conn.NewSubscriptionSet()                  // no error should be returned
	subChannel, subErrors := set.Subscribe()
	var activeSet = false

	for {
		// XXX: watch for an event for new units...
		// XXX: detect if startup enabled/disabled value changes...

		previous = invalid
		invalid = false

		// firstly, does service even exist or not?
		loadstate, err := conn.GetUnitProperty(service, "LoadState")
		if err != nil {
			log.Printf("Failed to get property: %v\n", err)
			invalid = true
		}

		if !invalid {
			var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
			if notFound { // XXX: in the loop we'll handle changes better...
				log.Printf("Failed to find service: %v\n", service)
				invalid = true // XXX ?
			}
		}

		if previous != invalid { // if invalid changed, send signal
			send = true
		}

		if invalid {
			log.Printf("Waiting for: %v\n", service) // waiting for service to appear...
			if activeSet {
				activeSet = false
				set.Remove(service) // no return value should ever occur
			}

			select {
			case _ = <-buschan: // XXX wait for new units event to unstick
				// loop so that we can see the changed invalid signal
				log.Printf("Service[%v]->DaemonReload()\n", service)

			case exit := <-obj.Events:
				if exit == "exit" {
					return
				} else {
					log.Fatal("Unknown event: %v\n", exit)
				}
			}
		} else {
			if !activeSet {
				activeSet = true
				set.Add(service) // no return value should ever occur
			}

			log.Printf("Watching: %v\n", service) // attempting to watch...
			select {
			case event := <-subChannel:

				log.Printf("Service event: %+v\n", event)
				// NOTE: the value returned is a map for some reason...
				if event[service] != nil {
					// event[service].ActiveState is not nil
					if event[service].ActiveState == "active" {
						log.Printf("Service[%v]->Started()\n", service)
					} else if event[service].ActiveState == "inactive" {
						log.Printf("Service[%v]->Stopped!()\n", service)
					} else {
						log.Fatal("Unknown service state: ", event[service].ActiveState)
					}
				} else {
					// service stopped (and ActiveState is nil...)
					log.Printf("Service[%v]->Stopped\n", service)
				}
				send = true

			case err := <-subErrors:
				log.Println("error:", err)
				log.Fatal(err)
				v.Events <- fmt.Sprintf("service: %v", "error")

			case exit := <-obj.Events:
				if exit == "exit" {
					return
				} else {
					log.Fatal("Unknown event: %v\n", exit)
				}
			}
		}

		if send {
			send = false
			//log.Println("Sending event!")
			v.Events <- fmt.Sprintf("service(%v): %v", obj.Name, "event!") // FIXME: use struct
		}
	}
}