AppManager: App installation status monitoring
Change-Id: I64f4ae0d27892b74f8827a275907cb75da09a758
diff --git a/core/installer/server/appmanager/server.go b/core/installer/server/appmanager/server.go
index b26fc85..80f076a 100644
--- a/core/installer/server/appmanager/server.go
+++ b/core/installer/server/appmanager/server.go
@@ -22,6 +22,7 @@
"github.com/giolekva/pcloud/core/installer/cluster"
"github.com/giolekva/pcloud/core/installer/server"
"github.com/giolekva/pcloud/core/installer/soft"
+ "github.com/giolekva/pcloud/core/installer/status"
"github.com/giolekva/pcloud/core/installer/tasks"
)
@@ -38,19 +39,21 @@
}
type Server struct {
- l sync.Locker
- port int
- ssClient soft.Client
- repo soft.RepoIO
- m *installer.AppManager
- r installer.AppRepository
- fr installer.AppRepository
- reconciler *tasks.FixedReconciler
- h installer.HelmReleaseMonitor
- cnc installer.ClusterNetworkConfigurator
- vpnAPIClient installer.VPNAPIClient
- tasks map[string]*taskForward
- tmpl tmplts
+ l sync.Locker
+ port int
+ ssClient soft.Client
+ repo soft.RepoIO
+ m *installer.AppManager
+ r installer.AppRepository
+ fr installer.AppRepository
+ reconciler *tasks.FixedReconciler
+ h status.ResourceMonitor
+ im *status.InstanceMonitor
+ cnc installer.ClusterNetworkConfigurator
+ vpnAPIClient installer.VPNAPIClient
+ tasks map[string]*taskForward
+ tmpl tmplts
+ idToResources map[string]map[string][]status.Resource
}
type tmplts struct {
@@ -104,7 +107,8 @@
r installer.AppRepository,
fr installer.AppRepository,
reconciler *tasks.FixedReconciler,
- h installer.HelmReleaseMonitor,
+ h status.ResourceMonitor,
+ im *status.InstanceMonitor,
cnc installer.ClusterNetworkConfigurator,
vpnAPIClient installer.VPNAPIClient,
) (*Server, error) {
@@ -113,19 +117,21 @@
return nil, err
}
return &Server{
- l: &sync.Mutex{},
- port: port,
- ssClient: ssClient,
- repo: repo,
- m: m,
- r: r,
- fr: fr,
- reconciler: reconciler,
- h: h,
- cnc: cnc,
- vpnAPIClient: vpnAPIClient,
- tasks: make(map[string]*taskForward),
- tmpl: tmpl,
+ l: &sync.Mutex{},
+ port: port,
+ ssClient: ssClient,
+ repo: repo,
+ m: m,
+ r: r,
+ fr: fr,
+ reconciler: reconciler,
+ h: h,
+ im: im,
+ cnc: cnc,
+ vpnAPIClient: vpnAPIClient,
+ tasks: make(map[string]*taskForward),
+ tmpl: tmpl,
+ idToResources: make(map[string]map[string][]status.Resource),
}, nil
}
@@ -142,7 +148,7 @@
r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
- r.HandleFunc("/api/tasks/{instanceId}", s.handleTaskStatusAPI).Methods(http.MethodGet)
+ r.HandleFunc("/api/instance/{instanceId}/status", s.handleInstanceStatusAPI).Methods(http.MethodGet)
r.HandleFunc("/api/dodo-app/{instanceId}", s.handleDodoAppUpdate).Methods(http.MethodPut)
r.HandleFunc("/api/dodo-app", s.handleDodoAppInstall).Methods(http.MethodPost)
r.HandleFunc("/clusters/{cluster}/servers/{server}/remove", s.handleClusterRemoveServer).Methods(http.MethodPost)
@@ -207,6 +213,24 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} else {
+ var toMonitor []status.Resource
+ s.idToResources[instanceId] = map[string][]status.Resource{}
+ for _, r := range rr.Helm {
+ resource := status.Resource{
+ Type: status.ResourceHelmRelease,
+ ResourceRef: status.ResourceRef{
+ Name: r.Name,
+ Namespace: r.Namespace,
+ },
+ }
+ toMonitor = append(toMonitor, resource)
+ if tmp, ok := s.idToResources[instanceId][r.Id]; ok {
+ s.idToResources[instanceId][r.Id] = append(tmp, resource)
+ } else {
+ s.idToResources[instanceId][r.Id] = []status.Resource{resource}
+ }
+ }
+ s.im.Monitor(instanceId, toMonitor)
var cfg dodoAppRendered
if err := json.NewDecoder(bytes.NewReader(rr.RenderedRaw)).Decode(&cfg); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -255,6 +279,24 @@
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
+ var toMonitor []status.Resource
+ s.idToResources[instanceId] = map[string][]status.Resource{}
+ for _, r := range rr.Helm {
+ resource := status.Resource{
+ Type: status.ResourceHelmRelease,
+ ResourceRef: status.ResourceRef{
+ Name: r.Name,
+ Namespace: r.Namespace,
+ },
+ }
+ toMonitor = append(toMonitor, resource)
+ if tmp, ok := s.idToResources[instanceId][r.Id]; ok {
+ s.idToResources[instanceId][r.Id] = append(tmp, resource)
+ } else {
+ s.idToResources[instanceId][r.Id] = []status.Resource{resource}
+ }
+ }
+ s.im.Monitor(instanceId, toMonitor)
t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
if err == nil {
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
@@ -725,7 +767,69 @@
return ret
}
-func (s *Server) handleTaskStatusAPI(w http.ResponseWriter, r *http.Request) {
+type IdName struct {
+ Id string
+ Name string
+}
+
+type IdNameMap map[string]IdName
+
+type resourceOuts struct {
+ Outs map[string]struct {
+ PostgreSQL IdNameMap `json:"postgresql"`
+ MongoDB IdNameMap `json:"mongodb"`
+ Volume IdNameMap `json:"volume"`
+ Ingress IdNameMap `json:"ingress"`
+ } `json:"outs"`
+}
+
+type DodoResource struct {
+ Type string
+ Name string
+}
+
+type DodoResourceStatus struct {
+ Type string `json:"type"`
+ Name string `json:"name"`
+ Status string `json:"status"`
+}
+
+func orginize(raw []byte) (map[string]DodoResource, error) {
+ var outs resourceOuts
+ if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&outs); err != nil {
+ return nil, err
+ }
+ ret := map[string]DodoResource{}
+ for _, out := range outs.Outs {
+ for _, r := range out.PostgreSQL {
+ ret[r.Id] = DodoResource{
+ Type: "postgresql",
+ Name: r.Name,
+ }
+ }
+ for _, r := range out.MongoDB {
+ ret[r.Id] = DodoResource{
+ Type: "mongodb",
+ Name: r.Name,
+ }
+ }
+ for _, r := range out.Volume {
+ ret[r.Id] = DodoResource{
+ Type: "volume",
+ Name: r.Name,
+ }
+ }
+ for _, r := range out.Ingress {
+ ret[r.Id] = DodoResource{
+ Type: "ingress",
+ Name: r.Name,
+ }
+ }
+ }
+ return ret, nil
+}
+
+func (s *Server) handleInstanceStatusAPI(w http.ResponseWriter, r *http.Request) {
s.l.Lock()
defer s.l.Unlock()
instanceId, ok := mux.Vars(r)["instanceId"]
@@ -733,16 +837,40 @@
http.Error(w, "empty slug", http.StatusBadRequest)
return
}
- t, ok := s.tasks[instanceId]
- if !ok {
- http.Error(w, "task not found", http.StatusInternalServerError)
+ statuses, err := s.im.Get(instanceId)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if ok && t.task == nil {
- http.Error(w, "not found", http.StatusNotFound)
+ idStatus := map[string]status.Status{}
+ for id, resources := range s.idToResources[instanceId] {
+ st := status.StatusNoStatus
+ for _, resource := range resources {
+ if st < statuses[resource] {
+ st = statuses[resource]
+ }
+ }
+ idStatus[id] = st
+ }
+ s.repo.Pull()
+ rendered, err := s.m.AppRendered(instanceId)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- resources := extractResources(t.task)
+ idToResource, err := orginize(rendered)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ resources := []DodoResourceStatus{}
+ for id, st := range idStatus {
+ resources = append(resources, DodoResourceStatus{
+ Type: idToResource[id].Type,
+ Name: idToResource[id].Name,
+ Status: status.StatusString(st),
+ })
+ }
json.NewEncoder(w).Encode(resources)
}