Skip to content

Commit

Permalink
Merge pull request #3975 from MaxMcAdam/anax-3918
Browse files Browse the repository at this point in the history
Issue #3918 - Provide Agbot APIs to list compatible nodes for given c…
  • Loading branch information
LiilyZhang authored Jan 23, 2024
2 parents 2d31d0d + ae00485 commit d748ab4
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
138 changes: 138 additions & 0 deletions agreementbot/secure_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/open-horizon/anax/events"
"github.com/open-horizon/anax/exchange"
"github.com/open-horizon/anax/exchangecommon"
"github.com/open-horizon/anax/externalpolicy"
"github.com/open-horizon/anax/i18n"
"github.com/open-horizon/anax/worker"
"golang.org/x/text/message"
Expand Down Expand Up @@ -188,6 +189,8 @@ func (a *SecureAPI) listen() {
router.HandleFunc("/deploycheck/userinputcompatible", a.userinput_compatible).Methods("GET", "OPTIONS")
router.HandleFunc("/deploycheck/deploycompatible", a.deploy_compatible).Methods("GET", "OPTIONS")
router.HandleFunc("/deploycheck/secretbindingcompatible", a.secretbinding_compatible).Methods("GET", "OPTIONS")
router.HandleFunc("/compatibility/constraints/node/{policyType}", a.policyCompatibleNodeList).Methods("GET", "OPTIONS")
router.HandleFunc("/compatibility/patterns/node", a.patternCompatibleNodeList).Methods("GET", "OPTIONS")
router.HandleFunc("/org/{org}/secrets/user/{user}", a.userSecrets).Methods("LIST", "OPTIONS")
router.HandleFunc("/org/{org}/secrets/node/{node}", a.nodeSecrets).Methods("LIST", "OPTIONS")
router.HandleFunc("/org/{org}/secrets/user/{user}/node/{node}", a.nodeUserSecrets).Methods("LIST", "OPTIONS")
Expand Down Expand Up @@ -260,6 +263,141 @@ func (a *SecureAPI) haNodeNMPUpdateRequest(w http.ResponseWriter, r *http.Reques
}
}

func (a *SecureAPI) policyCompatibleNodeList(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
glog.V(5).Infof(APIlogString(fmt.Sprintf("/compatibility/constraints/node called.")))

matchingNodes := []string{}

if user_ec, _, msgPrinter, ok := a.processExchangeCred("/compatibility/constraints/node", UserTypeCred, w, r); ok {
body, _ := ioutil.ReadAll(r.Body)
if len(body) == 0 {
glog.Errorf(APIlogString(fmt.Sprintf("No input found.")))
writeResponse(w, msgPrinter.Sprintf("No input found."), http.StatusBadRequest)
} else if input, err := a.decodePolicyCompatibleNodeInputBody(body, msgPrinter); err != nil {
writeResponse(w, err.Error(), http.StatusBadRequest)
} else {
nodes, err := exchange.GetExchangeOrgDevices(user_ec.GetHTTPFactory(), input.NodeOrg, user_ec.GetExchangeId(), user_ec.GetExchangeToken(), user_ec.GetExchangeURL())
if err != nil {
writeResponse(w, msgPrinter.Sprintf("Failed to get nodes from the exchange."), http.StatusInternalServerError)
}

pathVars := mux.Vars(r)
policyType := pathVars["policyType"]
if policyType != "nmp" && policyType != "dp" {
writeResponse(w, msgPrinter.Sprintf("Invalid node policy type %v. Allowed types are \"dp\" or \"nmp\".", policyType), http.StatusBadRequest)
}

for nodeId, _ := range nodes {
pol, err := exchange.GetNodePolicy(user_ec, nodeId)
if err != nil {
writeResponse(w, msgPrinter.Sprintf("Failed to get node policy from the exchange."), http.StatusInternalServerError)
} else if pol == nil {
continue
}

nodePol := &externalpolicy.ExternalPolicy{}
if policyType == "nmp" {
nodePol = pol.GetManagementPolicy()
} else if policyType == "dp" {
nodePol = pol.GetDeploymentPolicy()
}

if err = (&input.Constraints).IsSatisfiedBy(nodePol.Properties); err == nil {
matchingNodes = append(matchingNodes, nodeId)
}
}
}
}
a.writeCompCheckResponse(w, matchingNodes, nil, nil)
case "OPTIONS":
w.Header().Set("Allow", "GET, OPTIONS")
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
return
}

type policyNodeCompatibleInputBody struct {
NodeOrg string `json:"node_org"`
Constraints externalpolicy.ConstraintExpression `json:"constraints"`
}

func (a *SecureAPI) decodePolicyCompatibleNodeInputBody(body []byte, msgPrinter *message.Printer) (*policyNodeCompatibleInputBody, error) {
var js map[string]interface{}
if err := json.Unmarshal(body, &js); err != nil {
glog.Errorf(APIlogString(fmt.Sprintf("Input body couldn't be deserialized to JSON object. %v", err)))
return nil, fmt.Errorf(msgPrinter.Sprintf("Input body couldn't be deserialized to JSON object. %v", err))
} else {
var input policyNodeCompatibleInputBody
if err := json.Unmarshal(body, &input); err != nil {
glog.Errorf(APIlogString(fmt.Sprintf("Input body couldn't be deserialized to policyNodeCompatibleInputBody object. %v", err)))
return nil, fmt.Errorf(msgPrinter.Sprintf("Input body couldn't be deserialized to policyNodeCompatibleInputBody object. %v", err))
} else {
return &input, nil
}
}
}

func (a *SecureAPI) patternCompatibleNodeList(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
glog.V(5).Infof(APIlogString(fmt.Sprintf("/compatibility/patterns/node called.")))

matchingNodes := []string{}

if user_ec, _, msgPrinter, ok := a.processExchangeCred("/compatibility/patterns/node", UserTypeCred, w, r); ok {
body, _ := ioutil.ReadAll(r.Body)
if len(body) == 0 {
glog.Errorf(APIlogString(fmt.Sprintf("No input found.")))
writeResponse(w, msgPrinter.Sprintf("No input found."), http.StatusBadRequest)
} else if input, err := a.decodePatternCompatibleNodeInputBody(body, msgPrinter); err != nil {
writeResponse(w, err.Error(), http.StatusBadRequest)
} else {
nodes, err := exchange.GetExchangeOrgDevices(user_ec.GetHTTPFactory(), input.NodeOrg, user_ec.GetExchangeId(), user_ec.GetExchangeToken(), user_ec.GetExchangeURL())
if err != nil {
writeResponse(w, msgPrinter.Sprintf("Failed to get nodes from the exchange."), http.StatusInternalServerError)
}
for nodeId, node := range nodes {
if cutil.SliceContains(input.Patterns, node.Pattern) {
matchingNodes = append(matchingNodes, nodeId)
}
}
}
}
a.writeCompCheckResponse(w, matchingNodes, nil, nil)
case "OPTIONS":
w.Header().Set("Allow", "GET, OPTIONS")
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
return
}

type patternNodeCompatibleInputBody struct {
NodeOrg string `json:"node_org"`
Patterns []string `json:"patterns"`
}

func (a *SecureAPI) decodePatternCompatibleNodeInputBody(body []byte, msgPrinter *message.Printer) (*patternNodeCompatibleInputBody, error) {
var js map[string]interface{}
if err := json.Unmarshal(body, &js); err != nil {
glog.Errorf(APIlogString(fmt.Sprintf("Input body couldn't be deserialized to JSON object. %v", err)))
return nil, fmt.Errorf(msgPrinter.Sprintf("Input body couldn't be deserialized to JSON object. %v", err))
} else {
var input patternNodeCompatibleInputBody
if err := json.Unmarshal(body, &input); err != nil {
glog.Errorf(APIlogString(fmt.Sprintf("Input body couldn't be deserialized to patternNodeCompatibleInputBody object. %v", err)))
return nil, fmt.Errorf(msgPrinter.Sprintf("Input body couldn't be deserialized to patternNodeCompatibleInputBody object. %v", err))
} else {
return &input, nil
}
}
}

// This function does policy compatibility check.
func (a *SecureAPI) policy_compatible(w http.ResponseWriter, r *http.Request) {

Expand Down
9 changes: 9 additions & 0 deletions exchange/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ func GetHTTPExchangePatternHandlerWithContext(cfg *config.HorizonConfig) Pattern
}
}

// A handler for getting all the nodes in an org from the exchange
type OrgDevicesHandler func(orgId string, credId string, token string) (map[string]Device, error)

func GetOrgDevicesHandler(orgId string, ec ExchangeContext) OrgDevicesHandler {
return func(orgId string, credId string, token string) (map[string]Device, error) {
return GetExchangeOrgDevices(ec.GetHTTPFactory(), orgId, ec.GetExchangeId(), ec.GetExchangeToken(), ec.GetExchangeURL())
}
}

// A handler for getting the device information from the exchange
type DeviceHandler func(id string, token string) (*Device, error)

Expand Down
14 changes: 14 additions & 0 deletions exchange/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,20 @@ func GetExchangeDevice(httpClientFactory *config.HTTPClientFactory, deviceId str
}
}

func GetExchangeOrgDevices(httpClientFactory *config.HTTPClientFactory, orgId string, credId string, credPasswd string, exchangeUrl string) (map[string]Device, error) {
glog.V(3).Infof(rpclogString(fmt.Sprintf("retrieving devices from org %v from exchange", orgId)))

var resp interface{}
resp = new(GetDevicesResponse)
targetURL := exchangeUrl + "orgs/" + orgId + "/nodes"

if err := InvokeExchangeRetryOnTransportError(httpClientFactory, "GET", targetURL, credId, credPasswd, nil, &resp); err != nil {
glog.Errorf(err.Error())
return nil, err
}
return resp.(*GetDevicesResponse).Devices, nil
}

type PutDeviceResponse map[string]string

type PostDeviceResponse struct {
Expand Down

0 comments on commit d748ab4

Please sign in to comment.