[WIP] K8s update and runtime package changes (#895)
* First commit: outline of K8s runtime package * Added poller. Added auto-updater into default runtime * Added build and updated Poller interface * Added comments and NewRuntime that accepts Options * DefaultPoller; Runtime options * First commit to add Kubernetes cruft * Add comments * Add micro- prefix to K8s runtime service names * Get rid of import cycles. Move K8s runtime into main runtime package * Major refactoring: Poller replaced by Notifier POller has been replaced by Notifier which returns a channel of events that can be consumed and acted upon. * Added runtime configuration options * K8s runtime is now Kubernetes runtime in dedicated pkg. Naming kung-fu. * Fix typo in command. * Fixed typo * Dont Delete service when runtime stops. runtime.Stop stops services; no need to double-stop * Track runtime services * Parse Unix timestamps properly * Added deployments into K8s client. Debug logging
This commit is contained in:
@@ -2,19 +2,18 @@ package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/runtime/package"
|
||||
"github.com/micro/go-micro/runtime/process"
|
||||
proc "github.com/micro/go-micro/runtime/process/os"
|
||||
"github.com/micro/go-micro/util/log"
|
||||
)
|
||||
|
||||
type runtime struct {
|
||||
sync.RWMutex
|
||||
// options configure runtime
|
||||
options Options
|
||||
// used to stop the runtime
|
||||
closed chan bool
|
||||
// used to start new services
|
||||
@@ -25,162 +24,38 @@ type runtime struct {
|
||||
services map[string]*service
|
||||
}
|
||||
|
||||
type service struct {
|
||||
sync.RWMutex
|
||||
// NewRuntime creates new local runtime and returns it
|
||||
func NewRuntime(opts ...Option) Runtime {
|
||||
// get default options
|
||||
options := Options{}
|
||||
|
||||
running bool
|
||||
closed chan bool
|
||||
err error
|
||||
// apply requested options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// output for logs
|
||||
output io.Writer
|
||||
|
||||
// service to manage
|
||||
*Service
|
||||
// process creator
|
||||
Process *proc.Process
|
||||
// Exec
|
||||
Exec *process.Executable
|
||||
// process pid
|
||||
PID *process.PID
|
||||
}
|
||||
|
||||
func newRuntime() *runtime {
|
||||
return &runtime{
|
||||
options: options,
|
||||
closed: make(chan bool),
|
||||
start: make(chan *service, 128),
|
||||
services: make(map[string]*service),
|
||||
}
|
||||
}
|
||||
|
||||
func newService(s *Service, c CreateOptions) *service {
|
||||
var exec string
|
||||
var args []string
|
||||
// Init initializes runtime options
|
||||
func (r *runtime) Init(opts ...Option) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if len(s.Exec) > 0 {
|
||||
parts := strings.Split(s.Exec, " ")
|
||||
exec = parts[0]
|
||||
args = []string{}
|
||||
|
||||
if len(parts) > 1 {
|
||||
args = parts[1:]
|
||||
}
|
||||
} else {
|
||||
// set command
|
||||
exec = c.Command[0]
|
||||
// set args
|
||||
if len(c.Command) > 1 {
|
||||
args = c.Command[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return &service{
|
||||
Service: s,
|
||||
Process: new(proc.Process),
|
||||
Exec: &process.Executable{
|
||||
Binary: &packager.Binary{
|
||||
Name: s.Name,
|
||||
Path: exec,
|
||||
},
|
||||
Env: c.Env,
|
||||
Args: args,
|
||||
},
|
||||
closed: make(chan bool),
|
||||
output: c.Output,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) streamOutput() {
|
||||
go io.Copy(s.output, s.PID.Output)
|
||||
go io.Copy(s.output, s.PID.Error)
|
||||
}
|
||||
|
||||
func (s *service) Running() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.running
|
||||
}
|
||||
|
||||
func (s *service) Start() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset
|
||||
s.err = nil
|
||||
s.closed = make(chan bool)
|
||||
|
||||
// TODO: pull source & build binary
|
||||
log.Debugf("Runtime service %s forking new process\n", s.Service.Name)
|
||||
p, err := s.Process.Fork(s.Exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the pid
|
||||
s.PID = p
|
||||
// set to running
|
||||
s.running = true
|
||||
|
||||
if s.output != nil {
|
||||
s.streamOutput()
|
||||
}
|
||||
|
||||
// wait and watch
|
||||
go s.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Stop() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
select {
|
||||
case <-s.closed:
|
||||
return nil
|
||||
default:
|
||||
close(s.closed)
|
||||
s.running = false
|
||||
if s.PID == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Process.Kill(s.PID)
|
||||
for _, o := range opts {
|
||||
o(&r.options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Error() error {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *service) Wait() {
|
||||
// wait for process to exit
|
||||
err := s.Process.Wait(s.PID)
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// save the error
|
||||
if err != nil {
|
||||
s.err = err
|
||||
}
|
||||
|
||||
// no longer running
|
||||
s.running = false
|
||||
}
|
||||
|
||||
func (r *runtime) run() {
|
||||
r.RLock()
|
||||
closed := r.closed
|
||||
r.RUnlock()
|
||||
|
||||
// run runs the runtime management loop
|
||||
func (r *runtime) run(events <-chan Event) {
|
||||
t := time.NewTicker(time.Second * 5)
|
||||
defer t.Stop()
|
||||
|
||||
@@ -205,19 +80,67 @@ func (r *runtime) run() {
|
||||
if service.Running() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: check service error
|
||||
log.Debugf("Starting %s", service.Name)
|
||||
log.Debugf("Runtime starting service %s", service.Name)
|
||||
if err := service.Start(); err != nil {
|
||||
log.Debugf("Runtime error starting %s: %v", service.Name, err)
|
||||
log.Debugf("Runtime error starting service %s: %v", service.Name, err)
|
||||
}
|
||||
case <-closed:
|
||||
// TODO: stop all the things
|
||||
case event := <-events:
|
||||
log.Debugf("Runtime received notification event: %v", event)
|
||||
// NOTE: we only handle Update events for now
|
||||
switch event.Type {
|
||||
case Update:
|
||||
// parse returned response to timestamp
|
||||
updateTimeStamp, err := strconv.ParseInt(event.Version, 10, 64)
|
||||
if err != nil {
|
||||
log.Debugf("Runtime error parsing update build time: %v", err)
|
||||
continue
|
||||
}
|
||||
buildTime := time.Unix(updateTimeStamp, 0)
|
||||
processEvent := func(event Event, service *Service) error {
|
||||
buildTimeStamp, err := strconv.ParseInt(service.Version, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
muBuild := time.Unix(buildTimeStamp, 0)
|
||||
if buildTime.After(muBuild) {
|
||||
if err := r.Update(service); err != nil {
|
||||
return err
|
||||
}
|
||||
service.Version = fmt.Sprintf("%d", buildTime.Unix())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
r.Lock()
|
||||
if len(event.Service) > 0 {
|
||||
service, ok := r.services[event.Service]
|
||||
if !ok {
|
||||
log.Debugf("Runtime unknown service: %s", event.Service)
|
||||
r.Unlock()
|
||||
continue
|
||||
}
|
||||
if err := processEvent(event, service.Service); err != nil {
|
||||
log.Debugf("Runtime error updating service %s: %v", event.Service, err)
|
||||
}
|
||||
r.Unlock()
|
||||
continue
|
||||
}
|
||||
// if blank service was received we update all services
|
||||
for _, service := range r.services {
|
||||
if err := processEvent(event, service.Service); err != nil {
|
||||
log.Debugf("Runtime error updating service %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
r.Unlock()
|
||||
}
|
||||
case <-r.closed:
|
||||
log.Debugf("Runtime stopped.")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new service which is then started by runtime
|
||||
func (r *runtime) Create(s *Service, opts ...CreateOption) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
@@ -244,6 +167,7 @@ func (r *runtime) Create(s *Service, opts ...CreateOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the service from the runtime and stops it
|
||||
func (r *runtime) Delete(s *Service) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
@@ -256,6 +180,7 @@ func (r *runtime) Delete(s *Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update attemps to update the service
|
||||
func (r *runtime) Update(s *Service) error {
|
||||
// delete the service
|
||||
if err := r.Delete(s); err != nil {
|
||||
@@ -266,6 +191,7 @@ func (r *runtime) Update(s *Service) error {
|
||||
return r.Create(s)
|
||||
}
|
||||
|
||||
// List returns a slice of all services tracked by the runtime
|
||||
func (r *runtime) List() ([]*Service, error) {
|
||||
var services []*Service
|
||||
r.RLock()
|
||||
@@ -278,6 +204,7 @@ func (r *runtime) List() ([]*Service, error) {
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Start starts the runtime
|
||||
func (r *runtime) Start() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
@@ -291,11 +218,22 @@ func (r *runtime) Start() error {
|
||||
r.running = true
|
||||
r.closed = make(chan bool)
|
||||
|
||||
go r.run()
|
||||
var events <-chan Event
|
||||
if r.options.Notifier != nil {
|
||||
var err error
|
||||
events, err = r.options.Notifier.Notify()
|
||||
if err != nil {
|
||||
// TODO: should we bail here?
|
||||
log.Debugf("Runtime failed to start update notifier")
|
||||
}
|
||||
}
|
||||
|
||||
go r.run(events)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the runtime
|
||||
func (r *runtime) Stop() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
@@ -318,7 +256,16 @@ func (r *runtime) Stop() error {
|
||||
log.Debugf("Runtime stopping %s", service.Name)
|
||||
service.Stop()
|
||||
}
|
||||
// stop the notifier too
|
||||
if r.options.Notifier != nil {
|
||||
return r.options.Notifier.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements stringer interface
|
||||
func (r *runtime) String() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user