From 5bae79d137c389d2d8bb416b2c958979274d1fe3 Mon Sep 17 00:00:00 2001 From: "sewon.oh" Date: Mon, 18 Mar 2019 20:12:51 +0900 Subject: [PATCH] [EDGE-224] Implement APIs for services status check. Add service status checking API. refer: http://suprem.sec.samsung.net/jira/browse/EDGE-224 --- src/restapi/v1/restapi.go | 77 ++++++ src/restapi/v1/routers.go | 21 ++ src/servicemgr/service_discovery.go | 235 ++++++++++++++++-- src/servicemgr/service_execution.go | 24 +- src/servicemgr/test/service_discovery_test.go | 160 ++++++++++++ src/servicemgr/types.go | 16 +- 6 files changed, 496 insertions(+), 37 deletions(-) create mode 100644 src/servicemgr/test/service_discovery_test.go diff --git a/src/restapi/v1/restapi.go b/src/restapi/v1/restapi.go index 1d59741..0df85e0 100644 --- a/src/restapi/v1/restapi.go +++ b/src/restapi/v1/restapi.go @@ -107,6 +107,7 @@ func APIV1ServicemgrServicesServiceIDDelete(w http.ResponseWriter, r *http.Reque writeJSONResponse(w, smbytes, http.StatusOK) } } + // APIV1ServicemgrEventServiceIDPost function func APIV1ServicemgrEventServiceIDPost(w http.ResponseWriter, r *http.Request) { log.Printf("[%s] APIV1ServicemgrEventServiceIDPost", logPrefix) @@ -153,6 +154,82 @@ func APIV1ServicemgrServicesServiceIDPost(w http.ResponseWriter, r *http.Request } } +// APIV1ServicemgrServicesGet function +func APIV1ServicemgrServicesGet(w http.ResponseWriter, r *http.Request) { + log.Printf("[%s] APIV1ServicemgrServicesGet", logPrefix) + + ret, err := servicemgr.ServiceList() + if err != nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + return + } + + json, err := json.Marshal(ret) + if err == nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, json, http.StatusOK) + } else { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + } +} + +// APIV1ServicemgrServicesGetByAppName function +func APIV1ServicemgrServicesGetByAppName(w http.ResponseWriter, r *http.Request) { + log.Printf("[%s] APIV1ServicemgrServicesGetByAppName", logPrefix) + + vars := mux.Vars(r) + appName := vars["appname"] + + ret, err := servicemgr.FindServiceByName(appName) + if err != nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + return + } + + json, err := json.Marshal(ret) + if err == nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, json, http.StatusOK) + } else { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + } +} + +// APIV1ServicemgrServicesGetByServiceID function +func APIV1ServicemgrServicesGetByServiceID(w http.ResponseWriter, r *http.Request) { + log.Printf("[%s] APIV1ServicemgrServicesGetByServiceID", logPrefix) + + vars := mux.Vars(r) + serviceID := vars["serviceid"] + + id, err := strconv.ParseUint(serviceID, 10, 64) + if err != nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + return + } + + ret, err := servicemgr.FindServiceByID(id) + if err != nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + return + } + + json, err := json.Marshal(ret) + if err == nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, json, http.StatusOK) + } else { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + writeJSONResponse(w, nil, http.StatusBadRequest) + } +} + func writeJSONResponse(w http.ResponseWriter, data []byte, status int) { log.Printf("[%s] writeJSONResponse: %s", logPrefix, data) w.Header().Set("Content-Type", "application/json; charset=UTF-8") diff --git a/src/restapi/v1/routers.go b/src/restapi/v1/routers.go index 558afc9..8ab65f6 100644 --- a/src/restapi/v1/routers.go +++ b/src/restapi/v1/routers.go @@ -142,4 +142,25 @@ var routes = Routes{ "/api/v1/servicemgr/services/{serviceid}", APIV1ServicemgrServicesServiceIDPost, }, + + Route{ + "APIV1ServicemgrServicesGet", + strings.ToUpper("Get"), + "/api/v1/servicemgr/services", + APIV1ServicemgrServicesGet, + }, + + Route{ + "APIV1ServicemgrServicesGetByAppName", + strings.ToUpper("Get"), + "/api/v1/servicemgr/services/{appname}", + APIV1ServicemgrServicesGetByAppName, + }, + + Route{ + "APIV1ServicemgrServicesGetByServiceID", + strings.ToUpper("Get"), + "/api/v1/servicemgr/services/{serviceid}", + APIV1ServicemgrServicesGetByServiceID, + }, } diff --git a/src/servicemgr/service_discovery.go b/src/servicemgr/service_discovery.go index 1d25051..1489c6f 100644 --- a/src/servicemgr/service_discovery.go +++ b/src/servicemgr/service_discovery.go @@ -1,68 +1,251 @@ package servicemgr import ( + "context" "fmt" + "strconv" + "strings" + "time" "github.com/grandcat/zeroconf" ) const ( - //pass & fail public enum + // pass & fail public enum Fail = 0 Pass = 1 + + // service type for ochestration. + serviceType = "_ochestration._tcp" + servicePort = 5737 ) -var exits map[string]chan int = make(map[string]chan int) +var exits = make(map[uint64]chan int) -func RegisterService(name string, domain string, port int, ret chan int) (int, error) { - if name == "" { - ret <- Fail - return Fail, fmt.Errorf("Missing service name") +func RegisterService(appName string, serviceID uint64, serviceName string, status string, ret chan error) { + if appName == "" { + ret <- fmt.Errorf("Missing app name") + return } - if domain == "" { - domain = "local" + if serviceName == "" { + ret <- fmt.Errorf("Missing service name") + return } - if port == 0 { - ret <- Fail - return Fail, fmt.Errorf("Missing service port") + if exits[serviceID] != nil { + ret <- fmt.Errorf("Service id is duplicated") + return } - var service string = "_" + name + "._tcp" + if status == "" { + ret <- fmt.Errorf("Missing service status") + return + } - server, err := zeroconf.Register(name, service, domain, port, []string{""}, nil) + appName = appName + "+" + strconv.FormatUint(serviceID, 10) + domain := "local" + text := []string{strconv.FormatUint(serviceID, 10), serviceName, status} + server, err := zeroconf.Register(appName, serviceType, domain, servicePort, text, nil) if err != nil { - return Fail, err + ret <- err + return } defer server.Shutdown() - exits[name] = make(chan int) - ret <- Pass + exits[serviceID] = make(chan int) + ret <- nil select { - case <-exits[name]: - server.Shutdown() + case <-exits[serviceID]: + fmt.Println("Service has been terminated") } - - return Pass, nil } -func RemoveService(name string) (int, error) { +func RemoveService(id uint64) error { done := make(chan int) - if exits[name] == nil { - return Fail, fmt.Errorf("Service name is invalid") + if exits[id] == nil { + return fmt.Errorf("Service id is invalid") } go func() { - exits[name] <- Pass + exits[id] <- Pass done <- Pass }() select { case <-done: - delete(exits, name) + delete(exits, id) + } + return nil +} + +func ServiceList() ([]AppReturnInfo, error) { + domain := "local" + + resolver, err := zeroconf.NewResolver(nil) + if err != nil { + return nil, err + } + + var data = make(map[string][]ServiceReturnInfo) //data[appname] + entries := make(chan *zeroconf.ServiceEntry) + go func(results <-chan *zeroconf.ServiceEntry) { + for entry := range results { + appName := strings.Split(entry.Instance, "+")[0] + var services []ServiceReturnInfo + if data[appName] != nil { + services = data[appName] + } + + strID := entry.Text[0] + if id, err := strconv.ParseUint(strID, 10, 64); err == nil { + services = append(services, ServiceReturnInfo{ + ServiceID: id, + ServiceName: entry.Text[1], + Status: entry.Text[2], + DeviceIP: entry.AddrIPv4[0].String()}) + } + data[appName] = services + } + }(entries) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(1)) + defer cancel() + err = resolver.Browse(ctx, serviceType, domain, entries) + if err != nil { + return nil, err + } + <-ctx.Done() + + var ret []AppReturnInfo + for key, value := range data { + ret = append(ret, AppReturnInfo{ + AppName: key, + ServiceList: value}) + } + + return ret, nil +} + +func FindServiceByName(name string) (AppReturnInfo, error) { + if name == "" { + return AppReturnInfo{}, fmt.Errorf("App name is invalid") + } + + domain := "local" + resolver, err := zeroconf.NewResolver(nil) + if err != nil { + return AppReturnInfo{}, err + } + + app := AppReturnInfo{ + AppName: name} + + entries := make(chan *zeroconf.ServiceEntry) + go func(results <-chan *zeroconf.ServiceEntry) { + for entry := range results { + strID := entry.Text[0] + if id, err := strconv.ParseUint(strID, 10, 64); err == nil && + name == strings.Split(entry.Instance, "+")[0] { + app.ServiceList = append(app.ServiceList, ServiceReturnInfo{ + ServiceID: id, + ServiceName: entry.Text[1], + Status: entry.Text[2], + DeviceIP: entry.AddrIPv4[0].String()}) + } + } + }(entries) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(1)) + defer cancel() + err = resolver.Browse(ctx, serviceType, domain, entries) + if err != nil { + return AppReturnInfo{}, err + } + + <-ctx.Done() + return app, nil +} + +func FindServiceByID(serviceID uint64) (ServiceReturnInfo, error) { + domain := "local" + + resolver, err := zeroconf.NewResolver(nil) + if err != nil { + return ServiceReturnInfo{}, err + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ret ServiceReturnInfo + entries := make(chan *zeroconf.ServiceEntry) + go func(results <-chan *zeroconf.ServiceEntry) { + for entry := range results { + if strconv.FormatUint(serviceID, 10) == entry.Text[0] { + ret.ServiceID = serviceID + ret.ServiceName = entry.Text[1] + ret.Status = entry.Text[2] + ret.DeviceIP = entry.AddrIPv4[0].String() + cancel() + return + } + } + }(entries) + + err = resolver.Browse(ctx, serviceType, domain, entries) + if err != nil { + return ServiceReturnInfo{}, err + } + <-ctx.Done() + + return ret, nil +} + +func ChangeServiceStatus(serviceID uint64, status string) error { + domain := "local" + + resolver, err := zeroconf.NewResolver(nil) + if err != nil { + return err + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var appName string + var service ServiceReturnInfo + entries := make(chan *zeroconf.ServiceEntry) + go func(results <-chan *zeroconf.ServiceEntry) { + for entry := range results { + if strconv.FormatUint(serviceID, 10) == entry.Text[0] { + appName = strings.Split(entry.Instance, "+")[0] + service.ServiceID = serviceID + service.ServiceName = entry.Text[1] + service.Status = entry.Text[2] + service.DeviceIP = entry.AddrIPv4[0].String() + cancel() + return + } + } + }(entries) + + err = resolver.Browse(ctx, serviceType, domain, entries) + if err != nil { + return err + } + <-ctx.Done() + + RemoveService(serviceID) + + ret := make(chan error) + go RegisterService(appName, serviceID, service.ServiceName, status, ret) + + if err := <-ret; err != nil { + return err } - return Pass, nil + + return nil } diff --git a/src/servicemgr/service_execution.go b/src/servicemgr/service_execution.go index 843b3d1..8f56380 100644 --- a/src/servicemgr/service_execution.go +++ b/src/servicemgr/service_execution.go @@ -56,24 +56,28 @@ func (p Service) execute() error { } func (p Service) registerService() { - registerCh := make(chan int) + registerCh := make(chan error) // portNumber is not meaningful number now, so use magic number temporarily - portNumber := 2838 - go RegisterService(p.serviceName, "", portNumber, registerCh) - if <-registerCh == Pass { - log.Println("Service Register is Success") + appName, err := GetAppName(p.serviceID) + if err != nil { + log.Println("[Fail] GetAppName is Failed") + } + + go RegisterService(appName, p.serviceID, p.serviceName, "todo", registerCh) + if err := <-registerCh; err == nil { + log.Println("[Success] Service Register is Success") } else { - log.Println("Service Register is Failed") + log.Println("[Fail] " + err.Error()) } } func (p Service) removeService() { - res, _ := RemoveService(p.serviceName) - if res == Pass { - log.Println("Service Unregister is Success") + err := RemoveService(p.serviceID) + if err == nil { + log.Println("[Success] Service Unregister is Success") } else { - log.Println("Service Unregister is Failed") + log.Println("[Fail] " + err.Error()) } } diff --git a/src/servicemgr/test/service_discovery_test.go b/src/servicemgr/test/service_discovery_test.go new file mode 100644 index 0000000..02100f8 --- /dev/null +++ b/src/servicemgr/test/service_discovery_test.go @@ -0,0 +1,160 @@ +package test + +import ( + "reflect" + sm "servicemgr" + "testing" +) + +var data = sm.AppReturnInfo{ + AppName: "GreetWorldApp", + ServiceList: []sm.ServiceReturnInfo{ + { + ServiceID: 1, + ServiceName: "hello world#1", + Status: "started", + DeviceIP: "10.113.175.144", + }, + { + ServiceID: 2, + ServiceName: "hello world#2", + Status: "progressing", + DeviceIP: "10.113.175.144", + }, + }, +} + +func TestServiceRegisterRemove(t *testing.T) { + ret := make(chan error) + + go sm.RegisterService(data.AppName, data.ServiceList[0].ServiceID, + data.ServiceList[0].ServiceName, data.ServiceList[0].Status, ret) + + if err := <-ret; err != nil { + t.Error(err) + } + + err := sm.RemoveService(data.ServiceList[0].ServiceID) + + if err != nil { + t.Error(err) + } +} + +func TestServiceList(t *testing.T) { + ret := make(chan error) + + for _, service := range data.ServiceList { + go sm.RegisterService(data.AppName, service.ServiceID, + service.ServiceName, service.Status, ret) + + if err := <-ret; err != nil { + t.Error(err) + } + } + + cmp, err := sm.ServiceList() + + t.Log(cmp[0]) + if len(cmp[0].ServiceList) != 2 { + t.Error("fail") + } + + for _, service := range data.ServiceList { + err = sm.RemoveService(service.ServiceID) + + if err != nil { + t.Error(err) + } + } +} + +func TestFindServiceByName(t *testing.T) { + ret := make(chan error) + + for _, service := range data.ServiceList { + go sm.RegisterService(data.AppName, service.ServiceID, + service.ServiceName, service.Status, ret) + + if err := <-ret; err != nil { + t.Error(err) + } + } + + cmp, err := sm.FindServiceByName(data.AppName) + + t.Log(cmp) + if len(cmp.ServiceList) != 2 { + t.Error("fail") + } + + for _, service := range data.ServiceList { + err = sm.RemoveService(service.ServiceID) + + if err != nil { + t.Error(err) + } + } +} + +func TestFindServiceByID(t *testing.T) { + ret := make(chan error) + + for _, service := range data.ServiceList { + go sm.RegisterService(data.AppName, service.ServiceID, + service.ServiceName, service.Status, ret) + + if err := <-ret; err != nil { + t.Error(err) + } + } + + cmp, err := sm.FindServiceByID(data.ServiceList[0].ServiceID) + + t.Log(cmp) + if !reflect.DeepEqual(cmp, data.ServiceList[0]) { + t.Error("[Fail] FindServiceByID is failed") + } + + for _, service := range data.ServiceList { + err = sm.RemoveService(service.ServiceID) + + if err != nil { + t.Error(err) + } + } +} + +func TestChangeServiceStatus(t *testing.T) { + ret := make(chan error) + + go sm.RegisterService(data.AppName, data.ServiceList[0].ServiceID, + data.ServiceList[0].ServiceName, data.ServiceList[0].Status, ret) + + if err := <-ret; err != nil { + t.Error(err) + } + + cmp, err := sm.FindServiceByID(data.ServiceList[0].ServiceID) + t.Log(cmp) + if !reflect.DeepEqual(cmp, data.ServiceList[0]) { + t.Error("[Fail] FindServiceByID is failed") + } + + err = sm.ChangeServiceStatus(cmp.ServiceID, "progressing") + if err != nil { + t.Error(err) + } + + cmp, err = sm.FindServiceByID(data.ServiceList[0].ServiceID) + t.Log(cmp) + if !reflect.DeepEqual(cmp.Status, "progressing") { + t.Error("[Fail] Status is not equal") + } + + err = sm.RemoveService(data.ServiceList[0].ServiceID) + + if err != nil { + t.Error(err) + } +} diff --git a/src/servicemgr/types.go b/src/servicemgr/types.go index 2a9955f..ce38f53 100644 --- a/src/servicemgr/types.go +++ b/src/servicemgr/types.go @@ -47,7 +47,7 @@ type ServiceExecutionResponse struct { ServiceList []ServiceExecutionItem `json:"ServiceList"` } -// ServiceExecutionItem structrue +// ServiceExecutionItem structure type ServiceExecutionItem struct { ID uint64 `json:"ID"` Time string `json:"Time"` @@ -63,3 +63,17 @@ type MsgFormat struct { type MsgHeader struct { Type string `json:"Type"` } + +// ServiceReturnInfo structure +type ServiceReturnInfo struct { + ServiceID uint64 `json:"ServiceID"` + ServiceName string `json:"ServiceName"` + Status string `json:"Status"` + DeviceIP string `json:"DeviceIP"` +} + +//AppReturnInfo structure +type AppReturnInfo struct { + AppName string `json:"AppName"` + ServiceList []ServiceReturnInfo `json:"ServiceList"` +} -- 2.34.1